Model Baker Updates

...
Added fps to book data.
Cleanup.
Preparing LOD generation.
This commit is contained in:
max 2021-01-14 00:45:25 +01:00
parent 322b50b2bd
commit d3b4d34409
20 changed files with 1009 additions and 838 deletions

View File

@ -0,0 +1,47 @@
using UnityEngine;
using UnityEditor;
namespace TAO.VertexAnimation.Editor
{
public static class AssetDatabaseUtils
{
public static bool HasChildAsset(Object parent, Object child)
{
var assets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(parent));
foreach (var a in assets)
{
if (a == child)
{
return true;
}
}
return false;
}
public static void RemoveChildAssets(Object parent, Object[] filter = null)
{
var assets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(parent));
foreach (var a in assets)
{
bool filterSkip = false;
foreach (var f in filter)
{
if (a == f)
{
filterSkip = true;
break;
}
}
if (!filterSkip && a != parent)
{
AssetDatabase.RemoveObjectFromAsset(a);
}
}
}
}
}

View File

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

View File

@ -68,6 +68,7 @@ namespace TAO.VertexAnimation.Editor
using (new EditorGUILayout.VerticalScope())
{
EditorGUILayout.LabelField("General", EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.PropertyField(editorData.FindPropertyRelative("fps"));
EditorGUILayout.PropertyField(editorData.FindPropertyRelative("maxFrames"));
}
}

View File

@ -0,0 +1,52 @@
using UnityEngine;
using UnityEditor;
namespace TAO.VertexAnimation.Editor
{
public static class AnimationPrefab
{
public static GameObject Create(string path, string name, Mesh[] meshes, Material material, float[] lodTransitions)
{
// Create parent.
GameObject parent = new GameObject(name, typeof(LODGroup), typeof(VA_AnimatorComponentAuthoring), typeof(Unity.Entities.ConvertToEntity));
// Create all LODs.
LOD[] lods = new LOD[meshes.Length];
for (int i = 0; i < meshes.Length; i++)
{
GameObject lod = new GameObject(string.Format("{0}_LOD{1}", name, i), typeof(MeshFilter), typeof(MeshRenderer));
var mf = lod.GetComponent<MeshFilter>();
mf.sharedMesh = meshes[i];
var mr = lod.GetComponent<MeshRenderer>();
mr.sharedMaterial = material;
lod.transform.SetParent(parent.transform);
lods[i] = new LOD(lodTransitions[i], new Renderer[1] { mr });
}
var lodGroup = parent.GetComponent<LODGroup>();
lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();
// Create prefab.
GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(parent, path, InteractionMode.AutomatedAction);
GameObject.DestroyImmediate(parent);
return prefab;
}
public static GameObject Create(string path, string name, Mesh[] meshes, Material material, AnimationCurve lodTransitions)
{
float[] lt = new float[meshes.Length];
for (int i = 0; i < lt.Length; i++)
{
lt[i] = lodTransitions.Evaluate((1.0f / lt.Length) * (i + 1));
}
return Create(path, name, meshes, material, lt);
}
}
}

View File

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

View File

@ -47,6 +47,12 @@ namespace TAO.VertexAnimation.Editor
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(serializedObject.FindProperty("generateAnimationBook"));
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("generateLODS"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("lodCurve"), new GUIContent(""));
}
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("generatePrefab"));
@ -57,34 +63,19 @@ namespace TAO.VertexAnimation.Editor
if (GUILayout.Button("Bake", GUILayout.Height(32)))
{
ClearBakedData();
modelBaker.Bake();
if (modelBaker.saveBakedDataToAsset)
{
SaveBakedData();
modelBaker.SaveAssets();
}
}
if (modelBaker.BakedData.mesh != null)
if (GUILayout.Button("Delete", EditorStyles.miniButtonRight))
{
using (new EditorGUILayout.HorizontalScope())
if (EditorUtility.DisplayDialog("Delete Assets", "Deleting assets will loose references within the project.", "Ok", "Cancel"))
{
if (GUILayout.Button("Save", EditorStyles.miniButtonLeft))
{
SaveBakedData();
}
if (GUILayout.Button("Clear", EditorStyles.miniButtonRight))
{
ClearBakedData();
}
}
if (modelBaker.prefab && GUILayout.Button("Remove Prefab"))
{
DeletePrefab();
modelBaker.DeleteSavedAssets();
}
}
}
@ -96,121 +87,5 @@ namespace TAO.VertexAnimation.Editor
EditorGUILayout.PropertyField(serializedObject.FindProperty("bakedData"));
}
}
private void SaveBakedData()
{
ClearBakedData();
AssetDatabase.AddObjectToAsset(modelBaker.BakedData.mesh, modelBaker);
foreach (var pm in modelBaker.BakedData.positionMaps)
{
AssetDatabase.AddObjectToAsset(pm, modelBaker);
}
AssetDatabase.SaveAssets();
if (modelBaker.generatePrefab)
{
GeneratePrefab();
}
if(modelBaker.generateAnimationBook)
{
GenerateBook();
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private void ClearBakedData()
{
var assets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(modelBaker));
foreach (var a in assets)
{
if (a != modelBaker)
{
AssetDatabase.RemoveObjectFromAsset(a);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private void DeletePrefab()
{
string path = AssetDatabase.GetAssetPath(modelBaker.prefab);
AssetDatabase.DeleteAsset(path);
AssetDatabase.Refresh();
}
private void GeneratePrefab()
{
string path = AssetDatabase.GetAssetPath(modelBaker);
int start = path.LastIndexOf('/');
path = path.Remove(start, path.Length - start);
path += "/" + modelBaker.name + ".prefab";
// Generate Material
modelBaker.material = new Material(modelBaker.materialShader);
modelBaker.material.name = modelBaker.name;
AssetDatabase.AddObjectToAsset(modelBaker.material, modelBaker);
// Generate Object.
if (!modelBaker.prefab)
{
GameObject go = new GameObject(modelBaker.model.name, typeof(MeshFilter), typeof(MeshRenderer));
modelBaker.prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, path, InteractionMode.AutomatedAction);
DestroyImmediate(go);
}
GameObject inst = PrefabUtility.InstantiatePrefab(modelBaker.prefab) as GameObject;
inst.GetComponent<MeshFilter>().sharedMesh = modelBaker.BakedData.mesh;
inst.GetComponent<MeshRenderer>().sharedMaterial = modelBaker.material;
// Save.
PrefabUtility.ApplyPrefabInstance(inst, InteractionMode.UserAction);
AssetDatabase.SaveAssets();
DestroyImmediate(inst);
}
private void GenerateBook()
{
if (!modelBaker.book)
{
modelBaker.book = CreateInstance<VA_AnimationBook>();
}
modelBaker.book.name = modelBaker.model.name;
modelBaker.book.editorData = new VA_AnimationBook.EditorData();
modelBaker.book.editorData.materials = new Material[1] { modelBaker.material };
foreach (Texture2D tex in modelBaker.BakedData.positionMaps)
{
modelBaker.book.editorData.animationPages.Add(new VA_AnimationBook.EditorAnimationPage
{
name = "",
frames = 0,
textures = new List<VA_AnimationBook.EditorTextureEntry>()
{
new VA_AnimationBook.EditorTextureEntry
{
texture2D = tex
}
}
});
}
VA_AssetBuilder.AutoFill(ref modelBaker.book);
AssetDatabase.AddObjectToAsset(modelBaker.book, modelBaker);
AssetDatabase.SaveAssets();
}
}
}

View File

@ -0,0 +1,169 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace TAO.VertexAnimation.Editor
{
[CreateAssetMenu(fileName = "new ModelBaker", menuName = "VA_ModelBaker/ModelBaker", order = 400)]
public class VA_ModelBaker : ScriptableObject
{
#if UNITY_EDITOR
// Input.
public GameObject model;
public AnimationClip[] animationClips;
[Range(1, 60)]
public int fps = 24;
public int textureWidth = 512;
public bool generateLODS = true;
public AnimationCurve lodCurve = new AnimationCurve(new Keyframe(0, 1), new Keyframe(1, 0.01f));
public bool saveBakedDataToAsset = true;
public bool generateAnimationBook = true;
public bool generatePrefab = true;
public Shader materialShader = null;
// Output.
public GameObject prefab = null;
public Material material = null;
public Mesh[] meshes = null;
public VA_AnimationBook book = null;
[SerializeField]
private AnimationBaker.BakedData bakedData;
public void Bake()
{
var target = Instantiate(model);
target.name = model.name;
target.ConbineAndConvertGameObject();
bakedData = target.Bake(animationClips, fps, textureWidth);
if (generateLODS)
{
// TODO: LODs.
meshes = new Mesh[1] { bakedData.mesh };
}
else
{
meshes = new Mesh[1] { bakedData.mesh };
}
DestroyImmediate(target);
}
public void SaveAssets()
{
AssetDatabaseUtils.RemoveChildAssets(this, new Object[2] { book, material });
// TODO: LODs
AssetDatabase.AddObjectToAsset(bakedData.mesh, this);
foreach (var pm in bakedData.positionMaps)
{
AssetDatabase.AddObjectToAsset(pm, this);
}
AssetDatabase.SaveAssets();
if (generatePrefab)
{
GeneratePrefab();
}
if (generateAnimationBook)
{
GenerateBook();
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public void DeleteSavedAssets()
{
// Remove assets.
var assets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(this));
foreach (var a in assets)
{
if (a != this)
{
AssetDatabase.RemoveObjectFromAsset(a);
}
}
// Delete prefab.
string path = AssetDatabase.GetAssetPath(prefab);
AssetDatabase.DeleteAsset(path);
// Clear variables.
prefab = null;
material = null;
meshes = null;
book = null;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public void GeneratePrefab()
{
string path = AssetDatabase.GetAssetPath(this);
int start = path.LastIndexOf('/');
path = path.Remove(start, path.Length - start);
path += "/" + name + ".prefab";
// Generate Material
if (!AssetDatabaseUtils.HasChildAsset(this, material))
{
material = AnimationMaterial.Create(name, materialShader);
AssetDatabase.AddObjectToAsset(material, this);
}
else
{
material.shader = materialShader;
}
// Generate Prefab
prefab = AnimationPrefab.Create(path, name, meshes, material, lodCurve);
}
public void GenerateBook()
{
if (!book)
{
book = CreateInstance<VA_AnimationBook>();
}
book.name = string.Format("{0}Book", name);
book.editorData = new VA_AnimationBook.EditorData
{
materials = new Material[1] { material }
};
foreach (Texture2D tex in bakedData.positionMaps)
{
book.editorData.animationPages.Add(new VA_AnimationBook.EditorAnimationPage
{
name = "",
frames = 0,
textures = new List<VA_AnimationBook.EditorTextureEntry>()
{
new VA_AnimationBook.EditorTextureEntry
{
texture2D = tex
}
}
});
}
VA_AssetBuilder.AutoFill(ref book);
if (!AssetDatabaseUtils.HasChildAsset(this, book))
{
AssetDatabase.AddObjectToAsset(book, this);
}
}
#endif
}
}

View File

@ -300,6 +300,13 @@ namespace TAO.VertexAnimation.Editor
book.editorData.maxFrames = maxFrames;
}
}
else if (p.StartsWith("FPS-"))
{
if (int.TryParse(p.Remove(0, 4), out int fps))
{
book.editorData.fps = fps;
}
}
}
}
book.editorData.animationPages[i] = ap;
@ -339,6 +346,7 @@ namespace TAO.VertexAnimation.Editor
{
book.playData = new VA_AnimationBook.PlayData
{
fps = book.editorData.fps,
maxFrames = book.editorData.maxFrames,
materials = book.editorData.materials
};

View File

@ -3,7 +3,9 @@
"rootNamespace": "TAO.VertexAnimation.Editor",
"references": [
"TAO.VertexAnimation",
"Unity.Collections"
"Unity.Collections",
"Unity.Entities",
"Unity.Entities.Hybrid"
],
"includePlatforms": [
"Editor"

View File

@ -31,12 +31,14 @@ namespace TAO.VertexAnimation
public int maxFrames;
public int textureWidth;
public int textureHeight;
public int fps;
// Create animation info and calculate values.
public AnimationInfo(Mesh mesh, int frames, int textureWidth)
public AnimationInfo(Mesh mesh, int frames, int textureWidth, int fps)
{
this.frames = frames;
this.textureWidth = textureWidth;
this.fps = fps;
rawFrameHeight = Mathf.CeilToInt((float)mesh.vertices.Length / this.textureWidth);
frameHeight = Mathf.NextPowerOfTwo(rawFrameHeight);
@ -72,7 +74,7 @@ namespace TAO.VertexAnimation
Mesh mesh = model.GetComponent<SkinnedMeshRenderer>().sharedMesh;
// Get the info for the biggest animation.
AnimationInfo animationInfo = new AnimationInfo(mesh, maxFrames, textureWidth);
AnimationInfo animationInfo = new AnimationInfo(mesh, maxFrames, textureWidth, fps);
foreach (AnimationClip ac in animationClips)
{
@ -128,15 +130,17 @@ namespace TAO.VertexAnimation
skinnedMeshRenderer.BakeMesh(sampledMesh);
sampledMesh.RecalculateBounds();
int x = 0;
for (int v = 0; v < sampledMesh.vertices.Length; v++)
{
Vector3 vert = sampledMesh.vertices[v];
Vector3 normal = sampledMesh.normals[v];
List<Vector3> verts = new List<Vector3>();
sampledMesh.GetVertices(verts);
List<Vector3> normals = new List<Vector3>();
sampledMesh.GetNormals(normals);
int x = 0;
for (int v = 0; v < verts.Count; v++)
{
positionMap.SetPixel(x, y,
new Color(vert.x, vert.y, vert.z,
VectorUtils.Float3ToFloat(normal))
new Color(verts[v].x, verts[v].y, verts[v].z,
VectorUtils.Float3ToFloat(normals[v]))
);
x++;
@ -151,17 +155,16 @@ namespace TAO.VertexAnimation
GameObject.DestroyImmediate(inst);
positionMap.name = string.Format("VA_N-{0}_F-{1}_MF-{2}", animationClip.name, animationInfo.frames, animationInfo.maxFrames);
positionMap.name = string.Format("VA_N-{0}_F-{1}_MF-{2}_FPS-{3}", animationClip.name, animationInfo.frames, animationInfo.maxFrames, animationInfo.fps);
positionMap.filterMode = FilterMode.Point;
// TODO: Make no longer readable.
positionMap.Apply(false, false);
positionMap.Apply(false, true);
return positionMap;
}
public static Vector2[] BakePositionUVs(this Mesh mesh, AnimationInfo animationInfo)
{
Vector2[] uv3 = new Vector2[mesh.vertices.Length];
Vector2[] uv3 = new Vector2[mesh.vertexCount];
float xOffset = 1.0f / animationInfo.textureWidth;
float yOffset = 1.0f / animationInfo.textureHeight;
@ -169,7 +172,7 @@ namespace TAO.VertexAnimation
float x = xOffset / 2.0f;
float y = yOffset / 2.0f;
for (int v = 0; v < mesh.vertices.Length; v++)
for (int v = 0; v < uv3.Length; v++)
{
uv3[v] = new Vector2(x, y);

View File

@ -0,0 +1,18 @@
using UnityEngine;
namespace TAO.VertexAnimation
{
public static class AnimationMaterial
{
public static Material Create(string name, Shader shader)
{
Material material = new Material(shader)
{
name = name,
enableInstancing = true
};
return material;
}
}
}

View File

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

View File

@ -1,4 +1,9 @@
using UnityEngine;
// References:
// https://forum.unity.com/threads/help-combining-and-manipulating-skinned-mesh-renderers-imported-from-blender.505078/
// http://wiki.unity3d.com/index.php/CombineSkinnedMeshes
// http://wiki.unity3d.com/index.php/SkinnedMeshCombiner
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,45 +0,0 @@
using UnityEngine;
namespace TAO.VertexAnimation
{
[CreateAssetMenu(fileName = "new ModelBaker", menuName = "VA_ModelBaker/ModelBaker", order = 400)]
public class VA_ModelBaker : ScriptableObject
{
public GameObject model;
public AnimationClip[] animationClips;
[Range(1, 60)]
public int fps = 24;
public int textureWidth = 512;
#if UNITY_EDITOR
public bool saveBakedDataToAsset = true;
public bool generateAnimationBook = true;
public bool generatePrefab = true;
public Shader materialShader = null;
public GameObject prefab = null;
public Material material = null;
public VA_AnimationBook book = null;
#endif
[SerializeField]
private AnimationBaker.BakedData bakedData;
public AnimationBaker.BakedData BakedData
{
get
{
return bakedData;
}
}
public void Bake()
{
var target = Instantiate(model);
target.ConbineAndConvertGameObject();
bakedData = target.Bake(animationClips, fps, textureWidth);
DestroyImmediate(target);
}
}
}

View File

@ -49,6 +49,7 @@ namespace TAO.VertexAnimation
public List<PlayTextureGroup> textureGroups = new List<PlayTextureGroup>();
public List<PlayAnimationPage> animationPages = new List<PlayAnimationPage>();
public int fps;
public int maxFrames;
public Material[] materials;
public List<Texture2DArray> texture2DArray = new List<Texture2DArray>();
@ -67,8 +68,9 @@ namespace TAO.VertexAnimation
name = ap.name,
frames = ap.frames,
maxFrames = maxFrames,
frameTime = 1.0f / maxFrames,
duration = 1.0f / maxFrames * ap.frames,
frameTime = 1.0f / maxFrames * fps,
// TODO: Frames -1 ?????
duration = 1.0f / maxFrames * (ap.frames - 1),
animationMapIndex = GetFirstAnimationMapIndex(in ap.textures, in textureGroups),
colorMapIndex = GetFirstColorMapIndex(in ap.textures, in textureGroups)
});
@ -134,6 +136,7 @@ namespace TAO.VertexAnimation
public List<EditorTextureGroup> textureGroups = new List<EditorTextureGroup>() { new EditorTextureGroup { shaderParamName = "_PositionMap", textureType = TextureType.AnimationMap, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Point, isLinear = false } };
public List<EditorAnimationPage> animationPages = new List<EditorAnimationPage>();
public int fps;
public int maxFrames;
public Material[] materials;
public List<Texture2DArray> texture2DArray = null;

View File

@ -12,7 +12,7 @@ namespace TAO.VertexAnimation
public int frames;
// The maximum of frames the texture holds.
public int maxFrames;
// 1.0f / maxFrames.
// 1.0f / fps.
public float frameTime;
// FrameTime * frames.
public float duration;