using System; using System.Collections.Generic; using System.Linq; using System.Text; using UniGLTF; using UniGLTF.MeshUtility; using UnityEngine; using VRMShaders; namespace VRM { public static class VRMEditorExporter { /// /// Editor向けのエクスポート処理 /// /// 出力先 /// エクスポート設定 public static byte[] Export(GameObject exportRoot, VRMMetaObject meta, VRMExportSettings settings) { List destroy = new List(); try { return Export(exportRoot, meta, settings, destroy); } finally { foreach (var x in destroy) { Debug.LogFormat("destroy: {0}", x.name); GameObject.DestroyImmediate(x); } } } static bool IsPrefab(GameObject go) { return !go.scene.IsValid(); } /// /// DeepCopy /// /// /// static BlendShapeAvatar CopyBlendShapeAvatar(BlendShapeAvatar src, bool removeUnknown) { var avatar = GameObject.Instantiate(src); avatar.Clips = new List(); foreach (var clip in src.Clips) { if (removeUnknown && clip.Preset == BlendShapePreset.Unknown) { continue; } avatar.Clips.Add(GameObject.Instantiate(clip)); } return avatar; } /// /// 使用されない BlendShape を間引いた Mesh を作成して置き換える /// /// /// static void ReplaceMesh(GameObject target, SkinnedMeshRenderer smr, BlendShapeAvatar copyBlendShapeAvatar) { Mesh mesh = smr.sharedMesh; if (mesh == null) return; if (mesh.blendShapeCount == 0) return; // Mesh から BlendShapeClip からの参照がある blendShape の index を集める var usedBlendshapeIndexArray = copyBlendShapeAvatar.Clips .SelectMany(clip => clip.Values) .Where(val => target.transform.Find(val.RelativePath) == smr.transform) .Select(val => val.Index) .Distinct() .ToArray(); var copyMesh = mesh.Copy(copyBlendShape: false); // 使われている BlendShape だけをコピーする foreach (var i in usedBlendshapeIndexArray) { var name = mesh.GetBlendShapeName(i); var vCount = mesh.vertexCount; var vertices = new Vector3[vCount]; var normals = new Vector3[vCount]; var tangents = new Vector3[vCount]; mesh.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents); copyMesh.AddBlendShapeFrame(name, 100f, vertices, normals, tangents); } // BlendShapeClip の BlendShapeIndex を更新する(前に詰める) var indexMapper = usedBlendshapeIndexArray .Select((x, i) => new { x, i }) .ToDictionary(pair => pair.x, pair => pair.i); foreach (var clip in copyBlendShapeAvatar.Clips) { for (var i = 0; i < clip.Values.Length; ++i) { var value = clip.Values[i]; if (target.transform.Find(value.RelativePath) != smr.transform) continue; value.Index = indexMapper[value.Index]; clip.Values[i] = value; } } // mesh を置き換える smr.sharedMesh = copyMesh; } static void ForceUniqueName(Transform transform, Dictionary nameCount) { for (int i = 2; i < 5000; ++i) { var sb = new StringBuilder(); sb.Append(transform.name); sb.Append('_'); sb.Append(i); var newName = sb.ToString(); if (!nameCount.ContainsKey(newName)) { Debug.LogWarningFormat("force rename {0} => {1}", transform.name, newName); transform.name = newName; nameCount.Add(newName, 1); return; } } throw new Exception("?"); } /// /// /// /// /// /// 作業が終わったらDestoryするべき一時オブジェクト static byte[] Export(GameObject exportRoot, VRMMetaObject meta, VRMExportSettings settings, List destroy) { var target = exportRoot; // 常にコピーする。シーンを変化させない target = GameObject.Instantiate(target); destroy.Add(target); var metaBehaviour = target.GetComponent(); if (metaBehaviour == null) { metaBehaviour = target.AddComponent(); metaBehaviour.Meta = meta; } if (metaBehaviour.Meta == null) { // 来ないはず throw new Exception("meta required"); } { // copy元 var animator = exportRoot.GetComponent(); var beforeTransforms = exportRoot.GetComponentsInChildren(); // copy先 var afterTransforms = target.GetComponentsInChildren(); // copy先のhumanoidBoneのリストを得る var bones = (HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones)); var humanTransforms = bones .Where(x => x != HumanBodyBones.LastBone) .Select(x => animator.GetBoneTransform(x)) .Where(x => x != null) .Select(x => afterTransforms[Array.IndexOf(beforeTransforms, x)]) // copy 先を得る .ToArray(); var nameCount = target.GetComponentsInChildren() .GroupBy(x => x.name) .ToDictionary(x => x.Key, x => x.Count()); foreach (var t in target.GetComponentsInChildren()) { if (humanTransforms.Contains(t)) { // keep original name continue; } if (nameCount[t.name] > 1) { // 重複するボーン名をリネームする ForceUniqueName(t, nameCount); } } } // 正規化 if (settings.PoseFreeze) { // BoneNormalizer.Execute は Copy を作って正規化する。UNDO無用 target = VRMBoneNormalizer.Execute(target, settings.ForceTPose); destroy.Add(target); } var fp = target.GetComponent(); // 元のBlendShapeClipに変更を加えないように複製 var proxy = target.GetComponent(); if (proxy != null) { var copyBlendShapeAvatar = CopyBlendShapeAvatar(proxy.BlendShapeAvatar, settings.ReduceBlendshapeClip); proxy.BlendShapeAvatar = copyBlendShapeAvatar; // BlendShape削減 if (settings.ReduceBlendshape) { foreach (SkinnedMeshRenderer smr in target.GetComponentsInChildren()) { // 未使用のBlendShapeを間引く ReplaceMesh(target, smr, copyBlendShapeAvatar); } } } // 出力 var sw = System.Diagnostics.Stopwatch.StartNew(); var data = new UniGLTF.ExportingGltfData(); using (var exporter = new VRMExporter(data, settings.MeshExportSettings)) { exporter.Prepare(target); exporter.Export(new EditorTextureSerializer()); } var bytes = data.ToGlbBytes(); Debug.LogFormat("Export elapsed {0}", sw.Elapsed); return bytes; } } }