Merge pull request #1577 from ousttrue/fix/integrator_excludes

fix vrm mesh integration
This commit is contained in:
ousttrue
2022-03-07 15:06:28 +09:00
committed by GitHub
10 changed files with 292 additions and 165 deletions

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using UnityEngine;
namespace UniGLTF.MeshUtility
{
public class MeshExclude
{
List<Mesh> _excludes = new List<Mesh>();
public MeshExclude(IEnumerable<Mesh> excludes)
{
if (excludes != null)
{
_excludes.AddRange(excludes);
}
}
public bool IsExcluded(SkinnedMeshRenderer smr)
{
if (smr == null)
{
return true;
}
if (smr.sharedMesh == null)
{
return true;
}
if (_excludes.Contains(smr.sharedMesh))
{
Debug.LogFormat("{0} has excluded", smr);
return true;
}
return false;
}
public bool IsExcluded(MeshRenderer mr)
{
if (mr == null)
{
return true;
}
var filter = mr.GetComponent<MeshFilter>();
if (filter == null)
{
return true;
}
if (filter.sharedMesh == null)
{
return true;
}
if (_excludes.Contains(filter.sharedMesh))
{
Debug.LogFormat("{0} has excluded", mr);
return true;
}
return false;
}
}
}

View File

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

View File

@@ -6,6 +6,22 @@ namespace UniGLTF.MeshUtility
{
public static class MeshExtensions
{
public static Mesh GetMesh(this Renderer r)
{
if (r is SkinnedMeshRenderer smr)
{
return smr.sharedMesh;
}
if (r is MeshRenderer mr)
{
if (mr.GetComponent<MeshFilter>() is MeshFilter mf)
{
return mf.sharedMesh;
}
}
return null;
}
public static Mesh Copy(this Mesh src, bool copyBlendShape)
{
var dst = new Mesh();

View File

@@ -4,10 +4,18 @@ using UnityEngine;
namespace UniGLTF.MeshUtility
{
[System.Serializable]
public class MeshMap
{
public List<Mesh> Sources = new List<Mesh>();
public Mesh Integrated;
}
public class MeshIntegrationResult
{
public List<SkinnedMeshRenderer> SourceSkinnedMeshRenderers = new List<SkinnedMeshRenderer>();
public List<MeshRenderer> SourceMeshRenderers = new List<MeshRenderer>();
public SkinnedMeshRenderer IntegratedRenderer;
public MeshMap MeshMap = new MeshMap();
}
}

View File

@@ -6,13 +6,16 @@ namespace UniGLTF.MeshUtility
{
public class MeshIntegrator
{
public struct SubMesh
public const string INTEGRATED_MESH_NAME = "MeshesIntegrated";
public const string INTEGRATED_MESH_BLENDSHAPE_NAME = "MeshesBlendShapeIntegrated";
struct SubMesh
{
public List<int> Indices;
public Material Material;
}
public class BlendShape
class BlendShape
{
public int VertexOffset;
public string Name;
@@ -21,25 +24,18 @@ namespace UniGLTF.MeshUtility
public Vector3[] Normals;
public Vector3[] Tangents;
}
// public List<SkinnedMeshRenderer> Renderers { get; private set; }
public List<Vector3> Positions { get; private set; }
public List<Vector3> Normals { get; private set; }
public List<Vector2> UV { get; private set; }
public List<Vector4> Tangents { get; private set; }
public List<BoneWeight> BoneWeights { get; private set; }
public List<SubMesh> SubMeshes
{
get;
private set;
}
public List<Matrix4x4> BindPoses { get; private set; }
public List<Transform> Bones { get; private set; }
public List<BlendShape> BlendShapes { get; private set; }
public void AddBlendShapesToMesh(Mesh mesh)
public MeshIntegrationResult Result { get; } = new MeshIntegrationResult();
List<Vector3> Positions { get; } = new List<Vector3>();
List<Vector3> Normals { get; } = new List<Vector3>();
List<Vector2> UV { get; } = new List<Vector2>();
List<Vector4> Tangents { get; } = new List<Vector4>();
List<BoneWeight> BoneWeights { get; } = new List<BoneWeight>();
List<SubMesh> SubMeshes { get; } = new List<SubMesh>();
List<Matrix4x4> BindPoses { get; } = new List<Matrix4x4>();
List<Transform> Bones { get; } = new List<Transform>();
List<BlendShape> BlendShapes { get; } = new List<BlendShape>();
void AddBlendShapesToMesh(Mesh mesh)
{
Dictionary<string, BlendShape> map = new Dictionary<string, BlendShape>();
@@ -74,24 +70,6 @@ namespace UniGLTF.MeshUtility
}
}
public MeshIntegrator()
{
// Renderers = new List<SkinnedMeshRenderer>();
Positions = new List<Vector3>();
Normals = new List<Vector3>();
UV = new List<Vector2>();
Tangents = new List<Vector4>();
BoneWeights = new List<BoneWeight>();
SubMeshes = new List<SubMesh>();
BindPoses = new List<Matrix4x4>();
Bones = new List<Transform>();
BlendShapes = new List<BlendShape>();
}
static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset)
{
if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset;
@@ -115,6 +93,8 @@ namespace UniGLTF.MeshUtility
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
return;
}
Result.SourceMeshRenderers.Add(renderer);
Result.MeshMap.Sources.Add(mesh);
var indexOffset = Positions.Count;
var boneIndexOffset = Bones.Count;
@@ -142,7 +122,7 @@ namespace UniGLTF.MeshUtility
return;
}
var bindpose = bone.worldToLocalMatrix;
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length)
.Select(x => new BoneWeight()
{
@@ -150,10 +130,10 @@ namespace UniGLTF.MeshUtility
weight0 = 1,
})
);
BindPoses.Add(bindpose);
Bones.Add(bone);
for (int i = 0; i < mesh.subMeshCount; ++i)
{
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
@@ -182,8 +162,8 @@ namespace UniGLTF.MeshUtility
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
return;
}
// Renderers.Add(renderer);
Result.SourceSkinnedMeshRenderers.Add(renderer);
Result.MeshMap.Sources.Add(mesh);
var indexOffset = Positions.Count;
var boneIndexOffset = Bones.Count;
@@ -249,5 +229,55 @@ namespace UniGLTF.MeshUtility
});
}
}
public void Intgrate(bool onlyBlendShapeRenderers)
{
var mesh = new Mesh();
if (Positions.Count > ushort.MaxValue)
{
Debug.LogFormat("exceed 65535 vertices: {0}", Positions.Count);
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
}
mesh.vertices = Positions.ToArray();
mesh.normals = Normals.ToArray();
mesh.uv = UV.ToArray();
mesh.tangents = Tangents.ToArray();
mesh.boneWeights = BoneWeights.ToArray();
mesh.subMeshCount = SubMeshes.Count;
for (var i = 0; i < SubMeshes.Count; ++i)
{
mesh.SetIndices(SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i);
}
mesh.bindposes = BindPoses.ToArray();
if (onlyBlendShapeRenderers)
{
AddBlendShapesToMesh(mesh);
mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
}
else
{
mesh.name = INTEGRATED_MESH_NAME;
}
var meshNode = new GameObject();
if (onlyBlendShapeRenderers)
{
meshNode.name = "MeshIntegrator(BlendShape)";
}
else
{
meshNode.name = "MeshIntegrator";
}
var integrated = meshNode.AddComponent<SkinnedMeshRenderer>();
integrated.sharedMesh = mesh;
integrated.sharedMaterials = SubMeshes.Select(x => x.Material).ToArray();
integrated.bones = Bones.ToArray();
Result.IntegratedRenderer = integrated;
Result.MeshMap.Integrated = mesh;
}
}
}
}

View File

@@ -1,97 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UniGLTF.MeshUtility
{
public static class MeshIntegratorUtility
{
public const string INTEGRATED_MESH_NAME = "MeshesIntegrated";
public const string INTEGRATED_MESH_BLENDSHAPE_NAME = "MeshesBlendShapeIntegrated";
public static string INTEGRATED_MESH_NAME => MeshIntegrator.INTEGRATED_MESH_NAME;
public static string INTEGRATED_MESH_BLENDSHAPE_NAME => MeshIntegrator.INTEGRATED_MESH_BLENDSHAPE_NAME;
/// <summary>
/// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する
/// </summary>
/// <param name="go"></param>
/// <param name="onlyBlendShapeRenderers">BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer</param>
/// <returns></returns>
public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers)
public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers, IEnumerable<Mesh> excludes = null)
{
var result = new MeshIntegrationResult();
var exclude = new MeshExclude(excludes);
var meshNode = new GameObject();
if (onlyBlendShapeRenderers)
{
meshNode.name = "MeshIntegrator(BlendShape)";
}
else
{
meshNode.name = "MeshIntegrator";
}
meshNode.transform.SetParent(go.transform, false);
// レンダラから情報を集める
var integrator = new MeshUtility.MeshIntegrator();
if (onlyBlendShapeRenderers)
{
foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, true))
{
if (exclude.IsExcluded(x))
{
continue;
}
integrator.Push(x);
result.SourceSkinnedMeshRenderers.Add(x);
}
}
else
{
foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, false))
{
if (exclude.IsExcluded(x))
{
continue;
}
integrator.Push(x);
result.SourceSkinnedMeshRenderers.Add(x);
}
foreach (var x in EnumerateMeshRenderer(go.transform))
{
if (exclude.IsExcluded(x))
{
continue;
}
integrator.Push(x);
result.SourceMeshRenderers.Add(x);
}
}
var mesh = new Mesh();
if (integrator.Positions.Count > ushort.MaxValue)
{
Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count);
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
}
mesh.vertices = integrator.Positions.ToArray();
mesh.normals = integrator.Normals.ToArray();
mesh.uv = integrator.UV.ToArray();
mesh.tangents = integrator.Tangents.ToArray();
mesh.boneWeights = integrator.BoneWeights.ToArray();
mesh.subMeshCount = integrator.SubMeshes.Count;
for (var i = 0; i < integrator.SubMeshes.Count; ++i)
{
mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i);
}
mesh.bindposes = integrator.BindPoses.ToArray();
if (onlyBlendShapeRenderers)
{
integrator.AddBlendShapesToMesh(mesh);
mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
}
else
{
mesh.name = INTEGRATED_MESH_NAME;
}
var integrated = meshNode.AddComponent<SkinnedMeshRenderer>();
integrated.sharedMesh = mesh;
integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray();
integrated.bones = integrator.Bones.ToArray();
result.IntegratedRenderer = integrated;
return result;
integrator.Intgrate(onlyBlendShapeRenderers);
integrator.Result.IntegratedRenderer.transform.SetParent(go.transform, false);
return integrator.Result;
}
public static IEnumerable<SkinnedMeshRenderer> EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape)

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -12,7 +11,8 @@ using Object = UnityEngine.Object;
namespace VRM
{
/// <summary>
/// 複数のメッシュをまとめて、BlendShapeClipに変更を反映する
/// 複数のメッシュを統合する
/// VRM の場合、BlendShapeClip の調整が必用なのでこれも実行する。
/// </summary>
[DisallowMultipleComponent]
public static class MeshIntegratorEditor
@@ -26,28 +26,11 @@ namespace VRM
SkinnedMeshUtility.IsPrefab(Selection.activeObject);
}
public static void Integrate()
{
var go = Selection.activeObject as GameObject;
Integrate(go);
}
//[MenuItem("Assets/fooa", false)]
//private static void Foo()
//{
// var go = Selection.activeObject as GameObject;
// Debug.Log(SkinnedMeshUtility.IsPrefab(go));
//}
public static List<MeshIntegrationResult> Integrate(GameObject prefab)
public static List<MeshIntegrationResult> Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable<Mesh> excludes)
{
Undo.RecordObject(prefab, "Mesh Integration");
var instance = SkinnedMeshUtility.InstantiatePrefab(prefab);
var clips = new List<BlendShapeClip>();
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
if (proxy != null && proxy.BlendShapeAvatar != null)
@@ -63,17 +46,9 @@ namespace VRM
BackupVrmPrefab(prefab);
// Execute
var results = VRMMeshIntegratorUtility.Integrate(instance, clips);
var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes);
foreach (var res in results)
{
if (res.IntegratedRenderer == null) continue;
SaveMeshAsset(res.IntegratedRenderer.sharedMesh, instance, res.IntegratedRenderer.gameObject.name);
Undo.RegisterCreatedObjectUndo(res.IntegratedRenderer.gameObject, "Integrate Renderers");
}
// destroy source renderers
// disable source renderer
foreach (var res in results)
{
foreach (var renderer in res.SourceSkinnedMeshRenderers)
@@ -89,8 +64,32 @@ namespace VRM
}
}
foreach (var result in results)
{
if (result.IntegratedRenderer == null) continue;
var assetPath = writeAssetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}");
Debug.LogFormat("CreateAsset: {0}", assetPath);
assetPath.CreateAsset(result.IntegratedRenderer.sharedMesh);
Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers");
}
// Apply to Prefab
SkinnedMeshUtility.ApplyChangesToPrefab(instance);
var prefabPath = UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(prefab));
if (prefabPath.Equals(writeAssetPath))
{
SkinnedMeshUtility.ApplyChangesToPrefab(instance);
}
else
{
PrefabUtility.SaveAsPrefabAsset(instance, writeAssetPath.Value, out bool success);
if (!success)
{
throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {writeAssetPath}");
}
}
// destroy source renderers
Object.DestroyImmediate(instance);
return results;
@@ -143,17 +142,5 @@ namespace VRM
assetPath = assetPath.Replace(@"\", @"/");
return assetPath;
}
private static void SaveMeshAsset(Mesh mesh, GameObject go, string name)
{
var assetPath = Path.Combine(GetRootPrefabPath(go), string.Format("{0}{1}",
name,
ASSET_SUFFIX
));
Debug.LogFormat("CreateAsset: {0}", assetPath);
AssetDatabase.CreateAsset(mesh, assetPath);
}
}
}

View File

@@ -5,16 +5,16 @@ using System.Linq;
using System;
using System.Collections.Generic;
using UniGLTF.MeshUtility;
using System.IO;
namespace VRM
{
public class MeshIntegratorWizard : ScriptableWizard
{
const string MENU_KEY = "Assets/UnityEditorScripts/MeshIntegratorWizard";
[SerializeField]
GameObject m_root;
[Header("Validation")]
[SerializeField]
Material[] m_uniqueMaterials;
@@ -26,7 +26,7 @@ namespace VRM
public override bool Equals(object obj)
{
if(!(obj is MaterialKey))
if (!(obj is MaterialKey))
{
return base.Equals(obj);
}
@@ -57,13 +57,24 @@ namespace VRM
[SerializeField]
MaterialList[] m_duplicateMaterials;
[Header("Result")]
public MeshIntegrationResult[] integrationResults;
[MenuItem(MENU_KEY)]
static void CreateWizard()
[Serializable]
public class ExcludeItem
{
ScriptableWizard.DisplayWizard<MeshIntegratorWizard>("MeshIntegrator", "Integrate and close window", "Integrate");
public Mesh Mesh;
public bool Exclude;
}
[Header("Options")]
[SerializeField]
List<ExcludeItem> m_excludes = new List<ExcludeItem>();
[Header("Result")]
[SerializeField]
MeshMap[] integrationResults;
public static void CreateWizard()
{
ScriptableWizard.DisplayWizard<MeshIntegratorWizard>("MeshIntegratorWizard", "Integrate and close window", "Integrate");
}
private void OnEnable()
@@ -116,14 +127,17 @@ namespace VRM
void OnValidate()
{
Debug.Log("OnValidate");
if (m_root == null)
if (m_root == null
|| !PrefabUtility.IsPartOfAnyPrefab(m_root) || m_root.transform.parent != null)
{
Debug.LogWarning("Invalidate");
m_uniqueMaterials = new Material[] { };
m_duplicateMaterials = new MaterialList[] { };
m_excludes.Clear();
return;
}
Debug.Log("OnValidate");
m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false)
.SelectMany(x => x.sharedMaterials)
.Distinct()
@@ -135,6 +149,33 @@ namespace VRM
.Where(x => x.Materials.Length > 1)
.ToArray()
;
var exclude_map = new Dictionary<Mesh, ExcludeItem>();
var excludes = new List<ExcludeItem>();
foreach (var x in m_root.GetComponentsInChildren<Renderer>())
{
var mesh = x.GetMesh();
if (mesh == null)
{
continue;
}
var item = new ExcludeItem { Mesh = mesh };
excludes.Add(item);
exclude_map[mesh] = item;
}
foreach (var x in m_excludes)
{
if (exclude_map.TryGetValue(x.Mesh, out ExcludeItem item))
{
// update
item.Exclude = x.Exclude;
}
}
m_excludes.Clear();
foreach (var kv in exclude_map)
{
m_excludes.Add(kv.Value);
}
}
void OnWizardUpdate()
@@ -150,7 +191,22 @@ namespace VRM
return;
}
integrationResults = MeshIntegratorEditor.Integrate(m_root).ToArray();
var prefabPath = AssetDatabase.GetAssetPath(m_root);
var path = EditorUtility.SaveFilePanel("save prefab", Path.GetDirectoryName(prefabPath), m_root.name, "prefab");
if (string.IsNullOrEmpty(path))
{
return;
}
var assetPath = UniGLTF.UnityPath.FromFullpath(path);
if (!assetPath.IsUnderAssetsFolder)
{
Debug.LogWarning($"{path} is not asset path");
return;
}
var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh);
integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes).Select(x => x.MeshMap).ToArray();
}
void OnWizardCreate()

View File

@@ -27,11 +27,8 @@ namespace VRM
[MenuItem(UserMenuPrefix + "/Freeze T-Pose", priority = 20)]
private static void FreezeTPose() => VRMHumanoidNormalizerMenu.Normalize();
[MenuItem(UserMenuPrefix + "/MeshIntegration", validate = true)]
private static bool MeshIntegrationValidation() => MeshIntegratorEditor.IntegrateValidation();
[MenuItem(UserMenuPrefix + "/MeshIntegration", priority = 21)]
private static void MeshIntegration() => MeshIntegratorEditor.Integrate();
[MenuItem(UserMenuPrefix + "/MeshIntegratorWizard", priority = 21)]
private static void OpenMeshIntegratorWizard() => MeshIntegratorWizard.CreateWizard();
[MenuItem(UserMenuPrefix + "/Save SpringBone to JSON", validate = true)]
private static bool SaveSpringBoneToJsonValidation() => VRMSpringBoneUtilityEditor.SaveSpringBoneToJsonValidation();

View File

@@ -20,7 +20,7 @@ namespace VRM
if (avatar == null) return false;
var clips = avatar.Clips;
var results = Integrate(vrmRootObject, clips);
var results = Integrate(vrmRootObject, clips, null);
if (results.Any(x => x.IntegratedRenderer == null)) return false;
foreach (var result in results)
@@ -39,17 +39,17 @@ namespace VRM
return true;
}
public static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips)
public static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips, IEnumerable<Mesh> excludes)
{
var result = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false);
var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false, excludes: excludes);
if (withoutBlendShape.IntegratedRenderer != null)
{
result.Add(withoutBlendShape);
}
var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true);
var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true, excludes: excludes);
if (onlyBlendShape.IntegratedRenderer != null)
{
result.Add(onlyBlendShape);