MeshSimplifier

Basic setup to simplify meshes and generate LODs.
This commit is contained in:
max 2021-01-17 04:27:57 +01:00
parent d3b4d34409
commit 73d25ac453
8 changed files with 395 additions and 4 deletions

View File

@ -16,6 +16,8 @@ namespace TAO.VertexAnimation.Editor
public int textureWidth = 512;
public bool generateLODS = true;
// TODO: Improve curve/lod settings. LOD-Mesh Quality pair.
//public Vector2[] lodLevels = new Vector2[4] { new Vector2(32, 100), new Vector2(32, 65), new Vector2(32, 30), new Vector2(3, 0) };
public AnimationCurve lodCurve = new AnimationCurve(new Keyframe(0, 1), new Keyframe(1, 0.01f));
public bool saveBakedDataToAsset = true;
public bool generateAnimationBook = true;
@ -41,8 +43,7 @@ namespace TAO.VertexAnimation.Editor
if (generateLODS)
{
// TODO: LODs.
meshes = new Mesh[1] { bakedData.mesh };
meshes = bakedData.mesh.GenerateLOD(3, lodCurve);
}
else
{
@ -57,7 +58,10 @@ namespace TAO.VertexAnimation.Editor
AssetDatabaseUtils.RemoveChildAssets(this, new Object[2] { book, material });
// TODO: LODs
AssetDatabase.AddObjectToAsset(bakedData.mesh, this);
foreach (var m in meshes)
{
AssetDatabase.AddObjectToAsset(m, this);
}
foreach (var pm in bakedData.positionMaps)
{

View File

@ -243,5 +243,5 @@ namespace TAO.VertexAnimation
SkinnedMeshRenderer target = gameObject.AddComponent<SkinnedMeshRenderer>();
target.Combine(skinnedMeshes, meshes);
}
}
}
}

View File

@ -0,0 +1,33 @@
using UnityEngine;
namespace TAO.VertexAnimation
{
public static class MeshLodGenerator
{
public static Mesh[] GenerateLOD(this Mesh mesh, int lods, float[] quality)
{
Mesh[] lodMeshes = new Mesh[lods];
for (int lm = 0; lm < lodMeshes.Length; lm++)
{
lodMeshes[lm] = mesh.Copy();
lodMeshes[lm] = lodMeshes[lm].Simplify(quality[lm]);
lodMeshes[lm].name = string.Format("{0}_LOD{1}", lodMeshes[lm].name, lm);
}
return lodMeshes;
}
public static Mesh[] GenerateLOD(this Mesh mesh, int lods, AnimationCurve qualityCurve)
{
float[] quality = new float[lods];
for (int q = 0; q < quality.Length; q++)
{
quality[q] = qualityCurve.Evaluate(1f / quality.Length * q);
}
return GenerateLOD(mesh, lods, quality);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c65b5fa78d8074a439c6ba86df4c4ffc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,289 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace TAO.VertexAnimation
{
public static class MeshSimplifier
{
// Convert mesh into Triangles.
// Change triangles.
// Generate new mesh data based on Triangles.
// Everything is basically done through the triangle.
// When something changes in the triangle all correlated sub data changes as well (uv, normals, verts, etc).
public class Triangle
{
// Vertices (Vector3)
// Normals (Vector3)
// UVs (UV0, UV1, ..., UV7)
// Other...
public List<Vector3> vertices = new List<Vector3>(3);
public List<Vector3> normals = new List<Vector3>(3);
public Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
public float Perimeter()
{
return Vector3.Distance(vertices[0], vertices[1]) + Vector3.Distance(vertices[1], vertices[2]) + Vector3.Distance(vertices[2], vertices[0]);
}
// If two or more points have the same values the triangle has no surface area and will be 'zero'.
public bool IsZero()
{
if (vertices[0] == vertices[1] || vertices[0] == vertices[2] || vertices[1] == vertices[2])
{
return true;
}
return false;
}
// Returns the closest vertex index of a vertex within this triangle.
public int GetClosestVertexIndex(Vector3 vertex)
{
float distance = Mathf.Infinity;
int closestVertex = -1;
for (int v = 0; v < vertices.Count; v++)
{
if (vertices[v] != vertex)
{
float dist = Vector3.Distance(vertices[v], vertex);
if (dist < distance)
{
distance = dist;
closestVertex = v;
}
}
}
return closestVertex;
}
// Update triangle by copying data from a point in this triangle.
public bool UpdateVertex(int curVertexIndex, int newVertexIndex)
{
vertices[curVertexIndex] = vertices[newVertexIndex];
normals[curVertexIndex] = normals[newVertexIndex];
foreach (var uv in uvs)
{
uv.Value[curVertexIndex] = uv.Value[newVertexIndex];
}
return true;
}
// Update triangle by copying data from an other triangle.
public bool UpdateVertex(Triangle sourceTriangle, int sourceVertexIndex, int newSourceVertexIndex)
{
if (sourceTriangle != this)
{
Vector3 sourceVertex = sourceTriangle.vertices[sourceVertexIndex];
int index = vertices.IndexOf(sourceVertex);
if (index != -1)
{
// Set all the new data.
vertices[index] = sourceTriangle.vertices[newSourceVertexIndex];
normals[index] = sourceTriangle.normals[newSourceVertexIndex];
foreach (var uv in uvs)
{
uv.Value[index] = sourceTriangle.uvs[uv.Key][newSourceVertexIndex];
}
return true;
}
}
return false;
}
}
public static Mesh Simplify(this Mesh mesh, float quality)
{
string name = mesh.name;
List<Triangle> triangles = mesh.ToTriangles();
int targetCount = Mathf.FloorToInt(triangles.Count * quality);
int loopCount = 0;
while (triangles.Count > targetCount)
{
// Sort by perimeter.
// TODO: Better priority system. Maybe allow user to pass in method.
if (loopCount % triangles.Count == 0)
{
triangles.SortByPerimeter();
}
// Select tri/vert to simplify.
int curTriIndex = 0;
// TODO: Select vert by shortest total distance to the two other verts in the triangle.
int curVertIndex = 0;
Vector3 curVert = triangles[curTriIndex].vertices[curVertIndex];
// Select closest vert within triangle to merge into.
int newVertIndex = triangles[curTriIndex].GetClosestVertexIndex(curVert);
// Update all triangles.
// TODO: Apply only to connected triangles.
for (int t = 0; t < triangles.Count; t++)
{
triangles[t].UpdateVertex(triangles[curTriIndex], curVertIndex, newVertIndex);
}
triangles[curTriIndex].UpdateVertex(curVertIndex, newVertIndex);
// Remove all zero triangles.
triangles.RemoveAll(t => t.IsZero());
loopCount++;
}
mesh.Clear();
mesh = triangles.ToMesh();
mesh.name = name;
return mesh;
}
public static List<Triangle> ToTriangles(this Mesh mesh)
{
List<Triangle> triangles = new List<Triangle>();
List<Vector3> verts = new List<Vector3>(mesh.vertices);
List<Vector3> normals = new List<Vector3>(mesh.normals);
List<int> tris = new List<int>(mesh.triangles);
Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
for (int u = 0; u < 8; u++)
{
List<Vector2> coordinates = new List<Vector2>();
mesh.GetUVs(u, coordinates);
if (coordinates != null && coordinates.Any())
{
uvs.Add(u, coordinates);
}
}
for (int t = 0; t < tris.Count; t += 3)
{
Triangle tri = new Triangle();
tri.vertices.Add(verts[tris[t + 0]]);
tri.vertices.Add(verts[tris[t + 1]]);
tri.vertices.Add(verts[tris[t + 2]]);
tri.normals.Add(normals[tris[t + 0]]);
tri.normals.Add(normals[tris[t + 1]]);
tri.normals.Add(normals[tris[t + 2]]);
foreach (var uv in uvs)
{
if (tri.uvs.TryGetValue(uv.Key, out List<Vector2> coordinates))
{
coordinates.Add(uv.Value[tris[t + 0]]);
coordinates.Add(uv.Value[tris[t + 1]]);
coordinates.Add(uv.Value[tris[t + 2]]);
}
else
{
tri.uvs.Add(uv.Key, new List<Vector2>
{
uv.Value[tris[t + 0]],
uv.Value[tris[t + 1]],
uv.Value[tris[t + 2]]
});
}
}
triangles.Add(tri);
}
return triangles;
}
public static Mesh ToMesh(this List<Triangle> triangles)
{
Mesh mesh = new Mesh();
mesh.Clear();
List<Vector3> vertices = new List<Vector3>(triangles.Count * 3);
List<int> tris = new List<int>(triangles.Count * 3);
List<Vector3> normals = new List<Vector3>(triangles.Count * 3);
Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
int skipped = 0;
for (int t = 0; t < triangles.Count; t++)
{
for (int v = 0; v < triangles[t].vertices.Count; v++)
{
// Check for existing matching vert.
int vIndex = vertices.IndexOf(triangles[t].vertices[v]);
if (vIndex != -1)
{
// Check for existing matching normal.
if (normals[vIndex] == triangles[t].normals[v])
{
// We have a duplicate.
// Don't add the data and instead point to existing.
tris.Add(vIndex);
skipped++;
continue;
}
}
// Add data when it doesn't exist.
vertices.Add(triangles[t].vertices[v]);
normals.Add(triangles[t].normals[v]);
foreach (var uv in triangles[t].uvs)
{
if (uvs.TryGetValue(uv.Key, out List<Vector2> coordinates))
{
coordinates.Add(uv.Value[v]);
}
else
{
uvs.Add(uv.Key, new List<Vector2>
{
uv.Value[v],
});
}
}
tris.Add(t * 3 + v - skipped);
}
}
mesh.vertices = vertices.ToArray();
mesh.normals = normals.ToArray();
foreach (var uv in uvs)
{
mesh.SetUVs(uv.Key, uv.Value);
}
mesh.triangles = tris.ToArray();
mesh.Optimize();
mesh.RecalculateBounds();
mesh.RecalculateTangents();
return mesh;
}
public static List<Triangle> SortByPerimeter(this List<Triangle> triangles)
{
triangles.Sort((x, y) => x.Perimeter().CompareTo(y.Perimeter()));
return triangles;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a779acf4568ca24484403e1eb61b7f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,32 @@
using UnityEngine;
namespace TAO.VertexAnimation
{
public static class MeshUtils
{
public static Mesh Copy(this Mesh mesh)
{
Mesh copy = new Mesh
{
name = mesh.name,
vertices = mesh.vertices,
triangles = mesh.triangles,
normals = mesh.normals,
tangents = mesh.tangents,
colors = mesh.colors,
uv = mesh.uv,
uv2 = mesh.uv2,
uv3 = mesh.uv3,
uv4 = mesh.uv4,
uv5 = mesh.uv5,
uv6 = mesh.uv6,
uv7 = mesh.uv7,
uv8 = mesh.uv8,
};
copy.RecalculateBounds();
return copy;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af0818363d7ad6640b54b45d0b879c52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: