diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs new file mode 100644 index 000000000..7361e86f5 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + public class MeshExclude + { + List _excludes = new List(); + + public MeshExclude(IEnumerable 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(); + 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; + } + } +} diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs.meta new file mode 100644 index 000000000..186817aff --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshExclude.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ff8f78a008cc074daa97b33c95a7b16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs index a7377bbb5..9f28ce281 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs @@ -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() is MeshFilter mf) + { + return mf.sharedMesh; + } + } + return null; + } + public static Mesh Copy(this Mesh src, bool copyBlendShape) { var dst = new Mesh(); diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs index 56ffa4456..50e0d3679 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs @@ -4,10 +4,18 @@ using UnityEngine; namespace UniGLTF.MeshUtility { [System.Serializable] + public class MeshMap + { + public List Sources = new List(); + public Mesh Integrated; + } + public class MeshIntegrationResult { public List SourceSkinnedMeshRenderers = new List(); public List SourceMeshRenderers = new List(); public SkinnedMeshRenderer IntegratedRenderer; + + public MeshMap MeshMap = new MeshMap(); } } diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs index 76b4bffeb..569fa4bdf 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs @@ -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 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 Renderers { get; private set; } - public List Positions { get; private set; } - public List Normals { get; private set; } - public List UV { get; private set; } - public List Tangents { get; private set; } - public List BoneWeights { get; private set; } - public List SubMeshes - { - get; - private set; - } - - public List BindPoses { get; private set; } - public List Bones { get; private set; } - - public List BlendShapes { get; private set; } - public void AddBlendShapesToMesh(Mesh mesh) + public MeshIntegrationResult Result { get; } = new MeshIntegrationResult(); + List Positions { get; } = new List(); + List Normals { get; } = new List(); + List UV { get; } = new List(); + List Tangents { get; } = new List(); + List BoneWeights { get; } = new List(); + List SubMeshes { get; } = new List(); + List BindPoses { get; } = new List(); + List Bones { get; } = new List(); + List BlendShapes { get; } = new List(); + void AddBlendShapesToMesh(Mesh mesh) { Dictionary map = new Dictionary(); @@ -74,24 +70,6 @@ namespace UniGLTF.MeshUtility } } - public MeshIntegrator() - { -// Renderers = new List(); - - Positions = new List(); - Normals = new List(); - UV = new List(); - Tangents = new List(); - BoneWeights = new List(); - - SubMeshes = new List(); - - BindPoses = new List(); - Bones = new List(); - - BlendShapes = new List(); - } - 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(); + integrated.sharedMesh = mesh; + integrated.sharedMaterials = SubMeshes.Select(x => x.Material).ToArray(); + integrated.bones = Bones.ToArray(); + Result.IntegratedRenderer = integrated; + Result.MeshMap.Integrated = mesh; + } } -} \ No newline at end of file +} diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs index 5ae99d60a..48bffe5fe 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs @@ -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; + /// /// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する /// /// /// BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer /// - public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers) + public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers, IEnumerable 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(); - 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 EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape) diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs index 87a7f1665..793972b4c 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs @@ -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 { /// - /// 複数のメッシュをまとめて、BlendShapeClipに変更を反映する + /// 複数のメッシュを統合する。 + /// VRM の場合、BlendShapeClip の調整が必用なのでこれも実行する。 /// [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 Integrate(GameObject prefab) + public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes) { Undo.RecordObject(prefab, "Mesh Integration"); var instance = SkinnedMeshUtility.InstantiatePrefab(prefab); - var clips = new List(); var proxy = instance.GetComponent(); 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); - } - } } diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs index 03f67b694..9a1df032a 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs @@ -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("MeshIntegrator", "Integrate and close window", "Integrate"); + public Mesh Mesh; + public bool Exclude; + } + + [Header("Options")] + [SerializeField] + List m_excludes = new List(); + + [Header("Result")] + [SerializeField] + MeshMap[] integrationResults; + + public static void CreateWizard() + { + ScriptableWizard.DisplayWizard("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(); + var excludes = new List(); + foreach (var x in m_root.GetComponentsInChildren()) + { + 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() diff --git a/Assets/VRM/Editor/VrmTopMenu.cs b/Assets/VRM/Editor/VrmTopMenu.cs index dfd6bebd5..a79dc302a 100644 --- a/Assets/VRM/Editor/VrmTopMenu.cs +++ b/Assets/VRM/Editor/VrmTopMenu.cs @@ -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(); diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs index 29a0fb5af..81f61e0d5 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs @@ -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 Integrate(GameObject root, List blendshapeClips) + public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes) { var result = new List(); - 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);