From 787dada27e490b374d58014af73568599103d2ee Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Oct 2023 14:56:23 +0900 Subject: [PATCH 1/4] AvatarDescription.AddAnimator to CreateAvatarForCopyHierarchy --- .../Runtime/UniHumanoid/AvatarDescription.cs | 19 ++++++++----------- .../SkinnedMeshUtility/VRMBoneNormalizer.cs | 6 +++++- .../Runtime/MeshUtility/Vrm10MeshUtility.cs | 4 +++- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs index c3412d014..23a91c19b 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs @@ -273,12 +273,16 @@ namespace UniHumanoid } #endif - public static void AddAnimator(GameObject _src, + public static Avatar CreateAvatarForCopyHierarchy( + Animator src, GameObject dst, IDictionary boneMap, Action modAvatarDesc = null) { - var src = _src.GetComponent(); + if (src == null) + { + throw new ArgumentNullException("src"); + } var srcHumanBones = CachedEnum.GetValues() .Where(x => x != HumanBodyBones.LastBone) @@ -292,12 +296,6 @@ namespace UniHumanoid .ToDictionary(x => x.Key, x => boneMap[x.Value]) ; - var animator = dst.AddComponent(); - if (animator == null) - { - animator = dst.AddComponent(); - } - var avatarDescription = UniHumanoid.AvatarDescription.Create(); if (modAvatarDesc != null) { @@ -305,9 +303,8 @@ namespace UniHumanoid } avatarDescription.SetHumanBones(map); var avatar = avatarDescription.CreateAvatar(dst.transform); - - avatar.name = dst.name; - animator.avatar = avatar; + avatar.name = "created"; + return avatar; } } } \ No newline at end of file diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index 56fcca10b..3f5dcf927 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF; using UniGLTF.MeshUtility; using UniGLTF.Utils; using UniHumanoid; @@ -69,7 +70,8 @@ namespace VRM var (normalized, bMap) = BoneNormalizer.Execute(go); // 新しいヒエラルキーからAvatarを作る - UniHumanoid.AvatarDescription.AddAnimator(go, normalized, bMap, avatarDescription => + var newAvatar = UniHumanoid.AvatarDescription.CreateAvatarForCopyHierarchy( + go.GetComponent(), normalized, bMap, avatarDescription => { var vrmHuman = go.GetComponent(); if (vrmHuman != null && vrmHuman.Description != null) @@ -84,6 +86,8 @@ namespace VRM avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; } }); + var newAnimator = normalized.GetOrAddComponent(); + newAnimator.avatar = newAvatar; CopyVRMComponents(go, normalized, bMap); diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index da612bee9..883bfc674 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -133,7 +133,9 @@ namespace UniVRM10 // TODO: update: constraint // TODO: update: firstPoint offset - AvatarDescription.AddAnimator(go, normalized, boneMap); + var newAvatar = AvatarDescription.CreateAvatarForCopyHierarchy(go.GetComponent(), normalized, boneMap); + var newAnimator = normalized.GetOrAddComponent(); + newAnimator.avatar = newAvatar; // TODO: write back normalized transform to boneMap } From 4e1806078661e3d0798fa4ea4ab2495439eda05d Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Oct 2023 15:03:53 +0900 Subject: [PATCH 2/4] add ForceUniqueName. not implemented --- .../MeshUtility/Vrm10MeshUtilityDialog.cs | 7 ++++--- .../Runtime/MeshUtility/Vrm10MeshUtility.cs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs index 2b485805b..a7a58bcd9 100644 --- a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs @@ -80,7 +80,7 @@ namespace UniVRM10 { case Tabs.MeshFreeze: { - if (MeshBakeGui()) + if (MeshFreezeGui()) { modified = true; } @@ -135,12 +135,13 @@ namespace UniVRM10 return true; } - bool MeshBakeGui() + bool MeshFreezeGui() { + var forceUniqueName = ToggleIsModified("ForceUniqueName", ref _meshUtility.ForceUniqueName); var blendShape = ToggleIsModified("BlendShape", ref _meshUtility.FreezeBlendShape); var scale = ToggleIsModified("Scale", ref _meshUtility.FreezeScaling); var rotation = ToggleIsModified("Rotation", ref _meshUtility.FreezeRotation); - return blendShape || scale || rotation; + return forceUniqueName || blendShape || scale || rotation; } bool MeshIntegrateGui() diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index 883bfc674..e6df1aaad 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using UniGLTF.MeshUtility; @@ -17,6 +18,12 @@ namespace UniVRM10 /// public class Vrm10MeshUtility { + /// + /// GameObject 名が重複している場合にリネームする。 + /// 最初に実行(Avatar生成時のエラーを回避?) + /// + public bool ForceUniqueName = false; + /// /// Same as VRM-0 normalization /// - Mesh @@ -124,6 +131,11 @@ namespace UniVRM10 { // TODO: UNDO + if (ForceUniqueName) + { + throw new NotImplementedException(); + } + // 正規化されたヒエラルキーを作る var (normalized, boneMap) = BoneNormalizer.CreateNormalizedHierarchy(go, removeScaling: FreezeScaling, @@ -138,6 +150,14 @@ namespace UniVRM10 newAnimator.avatar = newAvatar; // TODO: write back normalized transform to boneMap + + foreach (var group in MeshIntegrationGroups) + { + foreach (var renderer in group.Renderers) + { + + } + } } } } From 16d17a79512118424dcc2439804bf49b1ce6bb2b Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Oct 2023 16:06:53 +0900 Subject: [PATCH 3/4] separate file --- .../Runtime/MeshUtility/BlendShapeReport.cs | 54 ++ .../MeshUtility/BlendShapeReport.cs.meta | 11 + .../Runtime/MeshUtility/BoneNormalizer.cs | 472 ++---------------- .../Runtime/MeshUtility/MeshFreezer.cs | 375 ++++++++++++++ .../Runtime/MeshUtility/MeshFreezer.cs.meta | 11 + .../SkinnedMeshUtility/VRMBoneNormalizer.cs | 2 +- Assets/VRM/Tests/NormalizeTests.cs | 4 +- .../Runtime/MeshUtility/Vrm10MeshUtility.cs | 5 +- 8 files changed, 498 insertions(+), 436 deletions(-) create mode 100644 Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs create mode 100644 Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs.meta create mode 100644 Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs create mode 100644 Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs.meta diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs b/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs new file mode 100644 index 000000000..61ce87f14 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + class BlendShapeReport + { + string m_name; + int m_count; + struct BlendShapeStat + { + public int Index; + public string Name; + public int VertexCount; + public int NormalCount; + public int TangentCount; + + public override string ToString() + { + return string.Format("[{0}]{1}: {2}, {3}, {4}\n", Index, Name, VertexCount, NormalCount, TangentCount); + } + } + List m_stats = new List(); + public int Count + { + get { return m_stats.Count; } + } + public BlendShapeReport(Mesh mesh) + { + m_name = mesh.name; + m_count = mesh.vertexCount; + } + public void SetCount(int index, string name, int v, int n, int t) + { + m_stats.Add(new BlendShapeStat + { + Index = index, + Name = name, + VertexCount = v, + NormalCount = n, + TangentCount = t, + }); + } + public override string ToString() + { + return String.Format("NormalizeSkinnedMesh: {0}({1}verts)\n{2}", + m_name, + m_count, + String.Join("", m_stats.Select(x => x.ToString()).ToArray())); + } + } +} \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs.meta new file mode 100644 index 000000000..491cfc2c5 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/BlendShapeReport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ab7e7ef8eaac5b4593a6a1102c15012 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs index 24d3caad0..5fbe48c35 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs @@ -1,16 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; -using VRMShaders; namespace UniGLTF.MeshUtility { public static class BoneNormalizer { - public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary boneMap); - public static (GameObject, Dictionary) CreateNormalizedHierarchy(GameObject go, bool removeScaling = true, bool removeRotation = true) @@ -74,430 +70,6 @@ namespace UniGLTF.MeshUtility } } - class BlendShapeReport - { - string m_name; - int m_count; - struct BlendShapeStat - { - public int Index; - public string Name; - public int VertexCount; - public int NormalCount; - public int TangentCount; - - public override string ToString() - { - return string.Format("[{0}]{1}: {2}, {3}, {4}\n", Index, Name, VertexCount, NormalCount, TangentCount); - } - } - List m_stats = new List(); - public int Count - { - get { return m_stats.Count; } - } - public BlendShapeReport(Mesh mesh) - { - m_name = mesh.name; - m_count = mesh.vertexCount; - } - public void SetCount(int index, string name, int v, int n, int t) - { - m_stats.Add(new BlendShapeStat - { - Index = index, - Name = name, - VertexCount = v, - NormalCount = n, - TangentCount = t, - }); - } - public override string ToString() - { - return String.Format("NormalizeSkinnedMesh: {0}({1}verts)\n{2}", - m_name, - m_count, - String.Join("", m_stats.Select(x => x.ToString()).ToArray())); - } - } - - /// - /// index が 有効であれば、setter に weight を渡す。無効であれば setter に 0 を渡す。 - /// - /// - /// - /// - /// - static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action setter) - { - if (srcIndex < 0 || srcIndex >= indexMap.Length) - { - // ありえるかどうかわからないが BoneWeight.boneIndexN に変な値が入っている. - setter(0, 0); - return false; - } - - var dstIndex = indexMap[srcIndex]; - if (dstIndex != -1) - { - // 有効なindex。weightをコピーする - setter(dstIndex, weight); - return true; - } - else - { - // 無効なindex。0でクリアする - setter(0, 0); - return false; - } - } - - /// - /// BoneWeight[] src から新しいボーンウェイトを作成する。 - /// - /// 変更前のBoneWeight[] - /// 新旧のボーンの対応表。新しい方は無効なボーンが除去されてnullの部分がある - /// 変更前のボーン配列 - /// 変更後のボーン配列。除去されたボーンがある場合、変更前より短い - /// - public static BoneWeight[] MapBoneWeight(BoneWeight[] src, - Dictionary boneMap, - Transform[] srcBones, - Transform[] dstBones - ) - { - // 処理前後の index の対応表を作成する - var indexMap = new int[srcBones.Length]; - for (int i = 0; i < srcBones.Length; ++i) - { - var srcBone = srcBones[i]; - if (srcBone == null) - { - // 元のボーンが無い - indexMap[i] = -1; - Debug.LogWarningFormat("bones[{0}] is null", i); - } - else - { - if (boneMap.TryGetValue(srcBone, out Transform dstBone)) - { - // 対応するボーンが存在する - var dstIndex = Array.IndexOf(dstBones, dstBone); - if (dstIndex == -1) - { - // ありえない。バグ - throw new Exception(); - } - indexMap[i] = dstIndex; - } - else - { - // 先のボーンが無い - indexMap[i] = -1; - Debug.LogWarningFormat("{0} is removed", srcBone.name); - } - } - } - - // 新しいBoneWeightを作成する - var newBoneWeights = new BoneWeight[src.Length]; - for (int i = 0; i < src.Length; ++i) - { - BoneWeight srcBoneWeight = src[i]; - - // 0 - CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex0, srcBoneWeight.weight0, (newIndex, newWeight) => - { - newBoneWeights[i].boneIndex0 = newIndex; - newBoneWeights[i].weight0 = newWeight; - }); - // 1 - CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex1, srcBoneWeight.weight1, (newIndex, newWeight) => - { - newBoneWeights[i].boneIndex1 = newIndex; - newBoneWeights[i].weight1 = newWeight; - }); - // 2 - CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex2, srcBoneWeight.weight2, (newIndex, newWeight) => - { - newBoneWeights[i].boneIndex2 = newIndex; - newBoneWeights[i].weight2 = newWeight; - }); - // 3 - CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex3, srcBoneWeight.weight3, (newIndex, newWeight) => - { - newBoneWeights[i].boneIndex3 = newIndex; - newBoneWeights[i].weight3 = newWeight; - }); - } - - return newBoneWeights; - } - - /// - /// srcのSkinnedMeshRendererを正規化して、dstにアタッチする - /// - /// 正規化前のSkinnedMeshRendererのTransform - /// 正規化後のSkinnedMeshRendererのTransform - /// 正規化前のボーンから正規化後のボーンを得る - static void NormalizeSkinnedMesh(Transform src, Transform dst, Dictionary boneMap) - { - var srcRenderer = src.GetComponent(); - if (srcRenderer == null - || !srcRenderer.enabled - || srcRenderer.sharedMesh == null - || srcRenderer.sharedMesh.vertexCount == 0) - { - // 有効なSkinnedMeshRendererが無かった - return; - } - - var srcMesh = srcRenderer.sharedMesh; - var originalSrcMesh = srcMesh; - - // 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る - var dstBones = srcRenderer.bones - .Where(x => x != null && boneMap.ContainsKey(x)) - .Select(x => boneMap[x]) - .ToArray(); - - var hasBoneWeight = srcRenderer.bones != null && srcRenderer.bones.Length > 0; - if (!hasBoneWeight) - { - // Before bake, bind no weight bones - //Debug.LogFormat("no weight: {0}", srcMesh.name); - - srcMesh = srcMesh.Copy(true); - var bw = new BoneWeight - { - boneIndex0 = 0, - boneIndex1 = 0, - boneIndex2 = 0, - boneIndex3 = 0, - weight0 = 1.0f, - weight1 = 0.0f, - weight2 = 0.0f, - weight3 = 0.0f, - }; - srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray(); - srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity }; - - srcRenderer.rootBone = srcRenderer.transform; - dstBones = new[] { boneMap[srcRenderer.transform] }; - srcRenderer.bones = new[] { srcRenderer.transform }; - srcRenderer.sharedMesh = srcMesh; - } - - // BakeMesh - var mesh = srcMesh.Copy(false); - mesh.name = srcMesh.name + ".baked"; - srcRenderer.BakeMesh(mesh); - - var blendShapeValues = new Dictionary(); - for (int i = 0; i < srcMesh.blendShapeCount; i++) - { - var val = srcRenderer.GetBlendShapeWeight(i); - if (val > 0) blendShapeValues.Add(i, val); - } - - // 新しい骨格のボーンウェイトを作成する - mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, srcRenderer.bones, dstBones); - - // recalc bindposes - mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray(); - - //var m = src.localToWorldMatrix; // include scaling - var m = default(Matrix4x4); - m.SetTRS(Vector3.zero, src.rotation, Vector3.one); // rotation only - mesh.ApplyMatrix(m); - - // - // BlendShapes - // - - // clear blendShape always - var backcup = new List(); - for (int i = 0; i < srcMesh.blendShapeCount; ++i) - { - backcup.Add(srcRenderer.GetBlendShapeWeight(i)); - srcRenderer.SetBlendShapeWeight(i, 0); - } - - var meshVertices = mesh.vertices; - var meshNormals = mesh.normals; - var meshTangents = Array.Empty(); - if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) - { - meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); - } - - var originalBlendShapePositions = new Vector3[meshVertices.Length]; - var originalBlendShapeNormals = new Vector3[meshVertices.Length]; - var originalBlendShapeTangents = new Vector3[meshVertices.Length]; - - var report = new BlendShapeReport(srcMesh); - var blendShapeMesh = new Mesh(); - for (int i = 0; i < srcMesh.blendShapeCount; ++i) - { - // check blendShape - srcRenderer.sharedMesh.GetBlendShapeFrameVertices(i, 0, originalBlendShapePositions, originalBlendShapeNormals, originalBlendShapeTangents); - var hasVertices = originalBlendShapePositions.Count(x => x != Vector3.zero); - var hasNormals = originalBlendShapeNormals.Count(x => x != Vector3.zero); - var hasTangents = 0; - if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) - { - hasTangents = originalBlendShapeTangents.Count(x => x != Vector3.zero); - } - var name = srcMesh.GetBlendShapeName(i); - if (string.IsNullOrEmpty(name)) - { - name = String.Format("{0}", i); - } - - report.SetCount(i, name, hasVertices, hasNormals, hasTangents); - - srcRenderer.SetBlendShapeWeight(i, 100.0f); - srcRenderer.BakeMesh(blendShapeMesh); - if (blendShapeMesh.vertices.Length != mesh.vertices.Length) - { - throw new Exception("different vertex count"); - } - - var value = blendShapeValues.ContainsKey(i) ? blendShapeValues[i] : 0; - srcRenderer.SetBlendShapeWeight(i, value); - - Vector3[] vertices = blendShapeMesh.vertices; - - for (int j = 0; j < vertices.Length; ++j) - { - if (originalBlendShapePositions[j] == Vector3.zero) - { - vertices[j] = Vector3.zero; - } - else - { - vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j]; - } - } - - Vector3[] normals = blendShapeMesh.normals; - for (int j = 0; j < normals.Length; ++j) - { - if (originalBlendShapeNormals[j] == Vector3.zero) - { - normals[j] = Vector3.zero; - - } - else - { - normals[j] = m.MultiplyVector(normals[j].normalized) - meshNormals[j]; - } - } - - Vector3[] tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray(); - if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) - { - for (int j = 0; j < tangents.Length; ++j) - { - if (originalBlendShapeTangents[j] == Vector3.zero) - { - tangents[j] = Vector3.zero; - } - else - { - tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j]; - } - } - } - - var frameCount = srcMesh.GetBlendShapeFrameCount(i); - for (int f = 0; f < frameCount; f++) - { - - var weight = srcMesh.GetBlendShapeFrameWeight(i, f); - - try - { - mesh.AddBlendShapeFrame(name, - weight, - vertices, - hasNormals > 0 ? normals : null, - hasTangents > 0 ? tangents : null - ); - } - catch (Exception) - { - Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}", - mesh.name, - srcMesh.GetBlendShapeName(i) - ); - throw; - } - } - } - - if (report.Count > 0) - { - Debug.LogFormat("{0}", report.ToString()); - } - - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - if (srcRenderer.rootBone != null) - { - if (boneMap.TryGetValue(srcRenderer.rootBone, out Transform found)) - { - dstRenderer.rootBone = found; - } - } - dstRenderer.bones = dstBones; - dstRenderer.sharedMesh = mesh; - - if (!hasBoneWeight) - { - // restore bones - srcRenderer.bones = new Transform[] { }; - srcRenderer.sharedMesh = originalSrcMesh; - } - // restore blendshape weights - for (int i = 0; i < backcup.Count; ++i) - { - srcRenderer.SetBlendShapeWeight(i, backcup[i]); - } - } - - /// - /// - /// - /// - /// - static void NormalizeNoneSkinnedMesh(Transform src, Transform dst) - { - var srcFilter = src.GetComponent(); - if (srcFilter == null - || srcFilter.sharedMesh == null - || srcFilter.sharedMesh.vertexCount == 0) - { - return; - } - - var srcRenderer = src.GetComponent(); - if (srcRenderer == null || !srcRenderer.enabled) - { - return; - } - - // Meshに乗っているボーンの姿勢を適用する - var dstFilter = dst.gameObject.AddComponent(); - - var dstMesh = srcFilter.sharedMesh.Copy(false); - dstMesh.ApplyRotationAndScale(src.localToWorldMatrix); - dstFilter.sharedMesh = dstMesh; - - // Materialをコピー - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - } /// /// 回転とスケールを除去したヒエラルキーのコピーを作成する(MeshをBakeする) @@ -506,12 +78,15 @@ namespace UniGLTF.MeshUtility /// BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する /// Avatarを作る関数 /// - public static (GameObject, Dictionary) Execute(GameObject go) + public static (GameObject, Dictionary) NormalizeHierarchyFreezeMesh(GameObject go, + bool removeScaling = true, + bool removeRotation = true + ) { // // 正規化されたヒエラルキーを作る // - var (normalized, boneMap) = CreateNormalizedHierarchy(go); + var (normalized, boneMap) = CreateNormalizedHierarchy(go, removeScaling, removeRotation); // // 各メッシュから回転・スケールを取り除いてBinding行列を再計算する @@ -524,9 +99,42 @@ namespace UniGLTF.MeshUtility continue; } - NormalizeSkinnedMesh(src, dst, boneMap); + { + // SkinnedMeshRenderer + var srcRenderer = src.GetComponent(); + var (mesh, dstBones) = MeshFreezer.NormalizeSkinnedMesh(srcRenderer, boneMap, dst.localToWorldMatrix); + if (mesh != null) + { + var dstRenderer = dst.gameObject.AddComponent(); + dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; + if (srcRenderer.rootBone != null) + { + if (boneMap.TryGetValue(srcRenderer.rootBone, out Transform found)) + { + dstRenderer.rootBone = found; + } + } + dstRenderer.bones = dstBones; + dstRenderer.sharedMesh = mesh; + } + } - NormalizeNoneSkinnedMesh(src, dst); + { + // MeshRenderer + var srcRenderer = src.GetComponent(); + if (srcRenderer != null) + { + var dstMesh = MeshFreezer.NormalizeNoneSkinnedMesh(srcRenderer); + if (dstMesh != null) + { + var dstFilter = dst.gameObject.AddComponent(); + dstFilter.sharedMesh = dstMesh; + // Materialをコピー + var dstRenderer = dst.gameObject.AddComponent(); + dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; + } + } + } } return (normalized, boneMap); diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs new file mode 100644 index 000000000..4e31c4e33 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VRMShaders; + +namespace UniGLTF.MeshUtility +{ + public static class MeshFreezer + { + /// + /// index が 有効であれば、setter に weight を渡す。無効であれば setter に 0 を渡す。 + /// + /// + /// + /// + /// + static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action setter) + { + if (srcIndex < 0 || srcIndex >= indexMap.Length) + { + // ありえるかどうかわからないが BoneWeight.boneIndexN に変な値が入っている. + setter(0, 0); + return false; + } + + var dstIndex = indexMap[srcIndex]; + if (dstIndex != -1) + { + // 有効なindex。weightをコピーする + setter(dstIndex, weight); + return true; + } + else + { + // 無効なindex。0でクリアする + setter(0, 0); + return false; + } + } + + /// + /// BoneWeight[] src から新しいボーンウェイトを作成する。 + /// + /// 変更前のBoneWeight[] + /// 新旧のボーンの対応表。新しい方は無効なボーンが除去されてnullの部分がある + /// 変更前のボーン配列 + /// 変更後のボーン配列。除去されたボーンがある場合、変更前より短い + /// + public static BoneWeight[] MapBoneWeight(BoneWeight[] src, + Dictionary boneMap, + Transform[] srcBones, + Transform[] dstBones + ) + { + // 処理前後の index の対応表を作成する + var indexMap = new int[srcBones.Length]; + for (int i = 0; i < srcBones.Length; ++i) + { + var srcBone = srcBones[i]; + if (srcBone == null) + { + // 元のボーンが無い + indexMap[i] = -1; + Debug.LogWarningFormat("bones[{0}] is null", i); + } + else + { + if (boneMap.TryGetValue(srcBone, out Transform dstBone)) + { + // 対応するボーンが存在する + var dstIndex = Array.IndexOf(dstBones, dstBone); + if (dstIndex == -1) + { + // ありえない。バグ + throw new Exception(); + } + indexMap[i] = dstIndex; + } + else + { + // 先のボーンが無い + indexMap[i] = -1; + Debug.LogWarningFormat("{0} is removed", srcBone.name); + } + } + } + + // 新しいBoneWeightを作成する + var newBoneWeights = new BoneWeight[src.Length]; + for (int i = 0; i < src.Length; ++i) + { + BoneWeight srcBoneWeight = src[i]; + + // 0 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex0, srcBoneWeight.weight0, (newIndex, newWeight) => + { + newBoneWeights[i].boneIndex0 = newIndex; + newBoneWeights[i].weight0 = newWeight; + }); + // 1 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex1, srcBoneWeight.weight1, (newIndex, newWeight) => + { + newBoneWeights[i].boneIndex1 = newIndex; + newBoneWeights[i].weight1 = newWeight; + }); + // 2 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex2, srcBoneWeight.weight2, (newIndex, newWeight) => + { + newBoneWeights[i].boneIndex2 = newIndex; + newBoneWeights[i].weight2 = newWeight; + }); + // 3 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex3, srcBoneWeight.weight3, (newIndex, newWeight) => + { + newBoneWeights[i].boneIndex3 = newIndex; + newBoneWeights[i].weight3 = newWeight; + }); + } + + return newBoneWeights; + } + + /// + /// + /// + /// + /// 正規化前のボーンから正規化後のボーンを得る + /// + /// + /// + public static (Mesh, Transform[]) NormalizeSkinnedMesh( + SkinnedMeshRenderer src, + Dictionary boneMap, + Matrix4x4 dstLocalToWorldMatrix) + { + if (src == null + || !src.enabled + || src.sharedMesh == null + || src.sharedMesh.vertexCount == 0) + { + // 有効なSkinnedMeshRendererが無かった + return default; + } + + var srcMesh = src.sharedMesh; + var originalSrcMesh = srcMesh; + + // 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る + var dstBones = src.bones + .Where(x => x != null && boneMap.ContainsKey(x)) + .Select(x => boneMap[x]) + .ToArray(); + + var hasBoneWeight = src.bones != null && src.bones.Length > 0; + if (!hasBoneWeight) + { + // Before bake, bind no weight bones + //Debug.LogFormat("no weight: {0}", srcMesh.name); + + srcMesh = srcMesh.Copy(true); + var bw = new BoneWeight + { + boneIndex0 = 0, + boneIndex1 = 0, + boneIndex2 = 0, + boneIndex3 = 0, + weight0 = 1.0f, + weight1 = 0.0f, + weight2 = 0.0f, + weight3 = 0.0f, + }; + srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray(); + srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity }; + + src.rootBone = src.transform; + dstBones = new[] { boneMap[src.transform] }; + src.bones = new[] { src.transform }; + src.sharedMesh = srcMesh; + } + + // BakeMesh + var mesh = srcMesh.Copy(false); + mesh.name = srcMesh.name + ".baked"; + src.BakeMesh(mesh); + + // 新しい骨格のボーンウェイトを作成する + mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, src.bones, dstBones); + + // recalc bindposes + mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dstLocalToWorldMatrix).ToArray(); + + //var m = src.localToWorldMatrix; // include scaling + var m = default(Matrix4x4); + m.SetTRS(Vector3.zero, src.transform.rotation, Vector3.one); // rotation only + mesh.ApplyMatrix(m); + + // + // BlendShapes + // + CopyBlendShapes(src, srcMesh, mesh, m); + + if (!hasBoneWeight) + { + // restore bones + src.bones = new Transform[] { }; + src.sharedMesh = originalSrcMesh; + } + + return (mesh, dstBones); + } + + private static void CopyBlendShapes(SkinnedMeshRenderer src, Mesh srcMesh, Mesh mesh, Matrix4x4 m) + { + var blendShapeValues = new Dictionary(); + for (int i = 0; i < srcMesh.blendShapeCount; i++) + { + var val = src.GetBlendShapeWeight(i); + if (val > 0) blendShapeValues.Add(i, val); + } + + // clear blendShape always + var backcup = new List(); + for (int i = 0; i < srcMesh.blendShapeCount; ++i) + { + backcup.Add(src.GetBlendShapeWeight(i)); + src.SetBlendShapeWeight(i, 0); + } + + var meshVertices = mesh.vertices; + var meshNormals = mesh.normals; + var meshTangents = Array.Empty(); + if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) + { + meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); + } + + var originalBlendShapePositions = new Vector3[meshVertices.Length]; + var originalBlendShapeNormals = new Vector3[meshVertices.Length]; + var originalBlendShapeTangents = new Vector3[meshVertices.Length]; + + var report = new BlendShapeReport(srcMesh); + var blendShapeMesh = new Mesh(); + for (int i = 0; i < srcMesh.blendShapeCount; ++i) + { + // check blendShape + src.sharedMesh.GetBlendShapeFrameVertices(i, 0, originalBlendShapePositions, originalBlendShapeNormals, originalBlendShapeTangents); + var hasVertices = originalBlendShapePositions.Count(x => x != Vector3.zero); + var hasNormals = originalBlendShapeNormals.Count(x => x != Vector3.zero); + var hasTangents = 0; + if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) + { + hasTangents = originalBlendShapeTangents.Count(x => x != Vector3.zero); + } + var name = srcMesh.GetBlendShapeName(i); + if (string.IsNullOrEmpty(name)) + { + name = String.Format("{0}", i); + } + + report.SetCount(i, name, hasVertices, hasNormals, hasTangents); + + src.SetBlendShapeWeight(i, 100.0f); + src.BakeMesh(blendShapeMesh); + if (blendShapeMesh.vertices.Length != mesh.vertices.Length) + { + throw new Exception("different vertex count"); + } + + var value = blendShapeValues.ContainsKey(i) ? blendShapeValues[i] : 0; + src.SetBlendShapeWeight(i, value); + + Vector3[] vertices = blendShapeMesh.vertices; + + for (int j = 0; j < vertices.Length; ++j) + { + if (originalBlendShapePositions[j] == Vector3.zero) + { + vertices[j] = Vector3.zero; + } + else + { + vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j]; + } + } + + Vector3[] normals = blendShapeMesh.normals; + for (int j = 0; j < normals.Length; ++j) + { + if (originalBlendShapeNormals[j] == Vector3.zero) + { + normals[j] = Vector3.zero; + + } + else + { + normals[j] = m.MultiplyVector(normals[j].normalized) - meshNormals[j]; + } + } + + Vector3[] tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray(); + if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) + { + for (int j = 0; j < tangents.Length; ++j) + { + if (originalBlendShapeTangents[j] == Vector3.zero) + { + tangents[j] = Vector3.zero; + } + else + { + tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j]; + } + } + } + + var frameCount = srcMesh.GetBlendShapeFrameCount(i); + for (int f = 0; f < frameCount; f++) + { + + var weight = srcMesh.GetBlendShapeFrameWeight(i, f); + + try + { + mesh.AddBlendShapeFrame(name, + weight, + vertices, + hasNormals > 0 ? normals : null, + hasTangents > 0 ? tangents : null + ); + } + catch (Exception) + { + Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}", + mesh.name, + srcMesh.GetBlendShapeName(i) + ); + throw; + } + } + } + + if (report.Count > 0) + { + Debug.LogFormat("{0}", report.ToString()); + } + // restore blendshape weights + for (int i = 0; i < backcup.Count; ++i) + { + src.SetBlendShapeWeight(i, backcup[i]); + } + } + + public static Mesh NormalizeNoneSkinnedMesh(MeshRenderer srcRenderer) + { + if (srcRenderer == null || !srcRenderer.enabled) + { + return default; + } + + var srcFilter = srcRenderer.GetComponent(); + if (srcFilter == null + || srcFilter.sharedMesh == null + || srcFilter.sharedMesh.vertexCount == 0) + { + return default; + } + + var dstMesh = srcFilter.sharedMesh.Copy(false); + // Meshに乗っているボーンの姿勢を適用する + dstMesh.ApplyRotationAndScale(srcRenderer.transform.localToWorldMatrix); + return dstMesh; + } + } +} diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs.meta new file mode 100644 index 000000000..33f6e2041 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09547456fd06d7b419d7553beba6f58a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index 3f5dcf927..e0901616a 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -67,7 +67,7 @@ namespace VRM } // 正規化されたヒエラルキーを作る - var (normalized, bMap) = BoneNormalizer.Execute(go); + var (normalized, bMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go); // 新しいヒエラルキーからAvatarを作る var newAvatar = UniHumanoid.AvatarDescription.CreateAvatarForCopyHierarchy( diff --git a/Assets/VRM/Tests/NormalizeTests.cs b/Assets/VRM/Tests/NormalizeTests.cs index ea8bd0e42..e683af574 100644 --- a/Assets/VRM/Tests/NormalizeTests.cs +++ b/Assets/VRM/Tests/NormalizeTests.cs @@ -59,7 +59,7 @@ namespace VRM map.Add(null, new GameObject("null")); // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある var boneWeights = map.CreateBoneWeight(64).ToArray(); - var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + var newBoneWeight = MeshFreezer.MapBoneWeight(boneWeights, map.Map, map.SrcBones.ToArray(), map.DstBones.ToArray()); // 正常系 @@ -75,7 +75,7 @@ namespace VRM map.Add(null, new GameObject("null")); // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある var boneWeights = map.CreateBoneWeight(64).ToArray(); - var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + var newBoneWeight = MeshFreezer.MapBoneWeight(boneWeights, map.Map, map.SrcBones.ToArray(), map.DstBones.ToArray()); // 4 つめが 0 になる diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index e6df1aaad..823a41955 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -137,7 +137,7 @@ namespace UniVRM10 } // 正規化されたヒエラルキーを作る - var (normalized, boneMap) = BoneNormalizer.CreateNormalizedHierarchy(go, + var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, removeScaling: FreezeScaling, removeRotation: FreezeRotation); @@ -151,6 +151,7 @@ namespace UniVRM10 // TODO: write back normalized transform to boneMap + // TODO: integration foreach (var group in MeshIntegrationGroups) { foreach (var renderer in group.Renderers) @@ -158,6 +159,8 @@ namespace UniVRM10 } } + + // TODO: split } } } From f769d696e1b5999be9450603e5628fd15b1076f2 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Oct 2023 16:42:26 +0900 Subject: [PATCH 4/4] =?UTF-8?q?NormalizeHierarchyFreezeMesh=20=E3=81=AB=20?= =?UTF-8?q?freezeBlendShape=20=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runtime/MeshUtility/BoneNormalizer.cs | 9 +++++++-- .../UniGLTF/Runtime/MeshUtility/MeshFreezer.cs | 18 +++++++++++++++++- .../Runtime/MeshUtility/Vrm10MeshUtility.cs | 3 ++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs index 5fbe48c35..bde593ee6 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs @@ -80,7 +80,8 @@ namespace UniGLTF.MeshUtility /// public static (GameObject, Dictionary) NormalizeHierarchyFreezeMesh(GameObject go, bool removeScaling = true, - bool removeRotation = true + bool removeRotation = true, + bool freezeBlendShape = true ) { // @@ -102,7 +103,11 @@ namespace UniGLTF.MeshUtility { // SkinnedMeshRenderer var srcRenderer = src.GetComponent(); - var (mesh, dstBones) = MeshFreezer.NormalizeSkinnedMesh(srcRenderer, boneMap, dst.localToWorldMatrix); + var (mesh, dstBones) = MeshFreezer.NormalizeSkinnedMesh( + srcRenderer, + boneMap, + dst.localToWorldMatrix, + freezeBlendShape); if (mesh != null) { var dstRenderer = dst.gameObject.AddComponent(); diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs index 4e31c4e33..77ea08113 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs @@ -132,7 +132,8 @@ namespace UniGLTF.MeshUtility public static (Mesh, Transform[]) NormalizeSkinnedMesh( SkinnedMeshRenderer src, Dictionary boneMap, - Matrix4x4 dstLocalToWorldMatrix) + Matrix4x4 dstLocalToWorldMatrix, + bool FreezeBlendShape = true) { if (src == null || !src.enabled @@ -179,6 +180,16 @@ namespace UniGLTF.MeshUtility src.sharedMesh = srcMesh; } + var blendShapeBackup = new List(); + if (!FreezeBlendShape) + { + for (int i = 0; i < srcMesh.blendShapeCount; ++i) + { + blendShapeBackup.Add(src.GetBlendShapeWeight(i)); + src.SetBlendShapeWeight(i, 0); + } + } + // BakeMesh var mesh = srcMesh.Copy(false); mesh.name = srcMesh.name + ".baked"; @@ -200,6 +211,11 @@ namespace UniGLTF.MeshUtility // CopyBlendShapes(src, srcMesh, mesh, m); + for (int i = 0; i < blendShapeBackup.Count; ++i) + { + src.SetBlendShapeWeight(i, blendShapeBackup[i]); + } + if (!hasBoneWeight) { // restore bones diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index 823a41955..9369b1f58 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -139,7 +139,8 @@ namespace UniVRM10 // 正規化されたヒエラルキーを作る var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, removeScaling: FreezeScaling, - removeRotation: FreezeRotation); + removeRotation: FreezeRotation, + freezeBlendShape: FreezeBlendShape); // TODO: update: spring // TODO: update: constraint