From a5d7346c5069a81417ee29bc395ade321bba3e71 Mon Sep 17 00:00:00 2001 From: ShiinaRinne Date: Wed, 30 Aug 2023 11:58:46 +0800 Subject: [PATCH] update readme --- Editor/AnimationHelpers.cs | 232 +++++++++++++++++++++---------------- README.md | 30 +++-- 2 files changed, 155 insertions(+), 107 deletions(-) diff --git a/Editor/AnimationHelpers.cs b/Editor/AnimationHelpers.cs index 3a38d22..45c8bdd 100644 --- a/Editor/AnimationHelpers.cs +++ b/Editor/AnimationHelpers.cs @@ -5,128 +5,166 @@ using System.Linq; using UnityEditor; using UnityEngine; +using UnityEngine.Rendering.Universal; -public class AnimationHelpers +namespace MMD6UnityTool { - [MenuItem("Assets/MMD/Create Morph Animation")] - public static void CreateMorphAnimation() + internal class AnimationHelpers { - System.GC.Collect(); - string path = - AssetDatabase.GetAssetPath(Selection.GetFiltered(SelectionMode.Assets).FirstOrDefault()); - - if (Path.GetExtension(path).ToUpper().Contains("VMD")) + [MenuItem("Assets/MMD/Create Morph Animation")] + internal static void CreateMorphAnimation() { - var stream = File.Open(path, FileMode.Open); + System.GC.Collect(); + string path = + AssetDatabase.GetAssetPath(Selection.GetFiltered(SelectionMode.Assets).FirstOrDefault()); - var vmd = VMDParser.ParseVMD(stream); + if (Path.GetExtension(path).ToUpper().Contains("VMD")) + { + var stream = File.Open(path, FileMode.Open); - var animationClip = new AnimationClip() {frameRate = 30}; + var vmd = VMDParser.ParseVMD(stream); - var delta = 1 / animationClip.frameRate; + var animationClip = new AnimationClip() {frameRate = 30}; - var keyframes = from keys in vmd.Morphs.ToLookup( - k => k.MorphName, - v => new Keyframe(v.FrameIndex * delta, v.Weight * 100)) - select keys; - - var gameobject = Selection.GetFiltered(SelectionMode.TopLevel).FirstOrDefault(); - var mesh = gameobject.GetComponent().sharedMesh; - var bsCounts = mesh.blendShapeCount; - var blendShapeNames = Enumerable.Range(0, bsCounts).ToList() - .ConvertAll(index => mesh.GetBlendShapeName(index)); + var delta = 1 / animationClip.frameRate; - foreach (var package in keyframes) - { - var name = package.Key; - - var curve = new AnimationCurve(package.ToArray()); - - var gameObjectName = gameobject.name; - var parentName = gameobject.transform.parent.name; - - - try + var keyframes = from keys in vmd.Morphs.ToLookup( + k => k.MorphName, + v => new Keyframe(v.FrameIndex * delta, v.Weight * 100)) + select keys; + + var gameobject = Selection.GetFiltered(SelectionMode.TopLevel).FirstOrDefault(); + var mesh = gameobject.GetComponent().sharedMesh; + var bsCounts = mesh.blendShapeCount; + var blendShapeNames = Enumerable.Range(0, bsCounts).ToList() + .ConvertAll(index => mesh.GetBlendShapeName(index)); + + foreach (var package in keyframes) { - string registerName = ""; - if (name == MorphAnimationNames.Blink) - { - registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink2).FirstOrDefault(); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); - registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink2Right).FirstOrDefault(); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); - } - else if (name == MorphAnimationNames.Smile) - { - registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink).FirstOrDefault(); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); - registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.WinkRight).FirstOrDefault(); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); - } - else if (name == MorphAnimationNames.Wink2 || name == MorphAnimationNames.Wink2Right || name == MorphAnimationNames.Wink || name == MorphAnimationNames.WinkRight) + var name = package.Key; + + var curve = new AnimationCurve(package.ToArray()); + + var gameObjectName = gameobject.name; + var parentName = gameobject.transform.parent.name; + + try { - registerName = blendShapeNames.Where(x => x.Split('.').Last() == name).FirstOrDefault(); - var existingCurve = AnimationUtility.GetEditorCurve(animationClip, EditorCurveBinding.FloatCurve($"{parentName}/{gameObjectName}", typeof(SkinnedMeshRenderer), $"blendShape.{registerName}")); - if (existingCurve != null) curve = MergeAnimationCurves(existingCurve, curve); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + string registerName = ""; + if (name == MorphAnimationNames.Blink) + { + registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink2).FirstOrDefault(); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink2Right).FirstOrDefault(); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + } + else if (name == MorphAnimationNames.Smile) + { + registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.Wink).FirstOrDefault(); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + var c = package.ToArray(); + Debug.Log(package.ToArray()); + registerName = blendShapeNames.Where(x => x.Split('.').Last() == MorphAnimationNames.WinkRight).FirstOrDefault(); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + } + else if (name == MorphAnimationNames.Wink2 || name == MorphAnimationNames.Wink2Right || + name == MorphAnimationNames.Wink || name == MorphAnimationNames.WinkRight) + { + registerName = blendShapeNames.Where(x => x.Split('.').Last() == name).FirstOrDefault(); + var existingCurve = AnimationUtility.GetEditorCurve(animationClip, EditorCurveBinding.FloatCurve($"{parentName}/{gameObjectName}", typeof(SkinnedMeshRenderer), $"blendShape.{registerName}")); + if (existingCurve != null) + curve = MergeAnimationCurves(existingCurve, curve); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + } + else + { + registerName = blendShapeNames.Where(x => x.Split('.').Last() == name).FirstOrDefault(); + AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + } } - else + catch (Exception e) { - registerName = blendShapeNames.Where(x => x.Split('.').Last() == name).FirstOrDefault(); - AddCurveToAnimationClip(animationClip, parentName, gameObjectName, blendShapeNames, registerName, curve); + Debug.LogError($"Error: {e.Message}"); } } - catch (Exception e) - { - Debug.LogError($"Error: {e.Message}"); - } + + stream.Close(); + AssetDatabase.CreateAsset(animationClip, path.Replace("vmd", "anim")); } - - stream.Close(); - AssetDatabase.CreateAsset(animationClip, path.Replace("vmd", "anim")); } - } - - private static void AddCurveToAnimationClip(AnimationClip animationClip, string parentName, string gameObjectName, List blendShapeNames, string registerName, AnimationCurve curve) - { - if (blendShapeNames.Contains(registerName)) + + + [MenuItem("Assets/MMD/Create Camera Animation")] + internal static void ExportCameraVmdToAnim() { - animationClip.SetCurve($"{parentName}/{gameObjectName}", typeof(SkinnedMeshRenderer), $"blendShape.{registerName}", curve); + var selected = Selection.activeObject; + string selectPath = AssetDatabase.GetAssetPath(selected); + if (!string.IsNullOrEmpty(selectPath)) + { + CameraVmdAgent camera_agent = new CameraVmdAgent(selectPath); + camera_agent.CreateAnimationClip(); + Debug.LogFormat("[{0}]:Export Camera Vmd Success!", System.DateTime.Now); + } + else + { + Debug.LogError("没有选中文件或文件夹"); + } } - } - private static AnimationCurve MergeAnimationCurves(AnimationCurve existingCurve, AnimationCurve newCurve) - { - var existingKeyframes = existingCurve.keys.ToList(); - existingKeyframes.AddRange(newCurve.keys); - return new AnimationCurve(existingKeyframes.ToArray()); - } + [MenuItem("GameObject/MMD/Generate MMDCamera")] + internal static void GenerateMMDCamera() + { + var mmdCamera = new GameObject("MMDCamera"); + var distance = new GameObject("Distance"); + var camera = new GameObject("Camera"); - private static class MorphAnimationNames - { - public const string Blink = "まばたき"; - public const string Wink2 = "ウィンク2"; - public const string Wink2Right = "ウィンク2右"; - public const string Smile = "笑い"; - public const string Wink = "ウィンク"; - public const string WinkRight = "ウィンク右"; - } - + distance.transform.parent = mmdCamera.transform; + camera.transform.parent = distance.transform; - [MenuItem("Assets/MMD/Create Camera Animation")] - public static void ExportCameraVmdToAnim() - { - var selected = Selection.activeObject; - string selectPath = AssetDatabase.GetAssetPath(selected); - if (!string.IsNullOrEmpty(selectPath)) + distance.transform.rotation = Quaternion.Euler(0, 180, 0); + + mmdCamera.AddComponent(); + camera.AddComponent(); + camera.AddComponent(); + + // Just a reference, you can change this part according to your own needs + + // =========== Enable postprocessing for unity urp =========== + // var uac = camera.gameObject.AddComponent(); + // uac.renderPostProcessing = true; + // uac.antialiasing = AntialiasingMode.SubpixelMorphologicalAntiAliasing; + // uac.antialiasingQuality = AntialiasingQuality.High; + } + + #region Utils + + private static void AddCurveToAnimationClip(AnimationClip animationClip, string parentName, + string gameObjectName, List blendShapeNames, string registerName, AnimationCurve curve) { - CameraVmdAgent camera_agent = new CameraVmdAgent(selectPath); - camera_agent.CreateAnimationClip(); - Debug.LogFormat("[{0}]:Export Camera Vmd Success!", System.DateTime.Now); + if (blendShapeNames.Contains(registerName)) + { + animationClip.SetCurve($"{parentName}/{gameObjectName}", typeof(SkinnedMeshRenderer), + $"blendShape.{registerName}", curve); + } } - else + + private static AnimationCurve MergeAnimationCurves(AnimationCurve existingCurve, AnimationCurve newCurve) { - Debug.LogError("没有选中文件或文件夹"); + var existingKeyframes = existingCurve.keys.ToList(); + existingKeyframes.AddRange(newCurve.keys); + return new AnimationCurve(existingKeyframes.ToArray()); } + + private static class MorphAnimationNames + { + public const string Blink = "まばたき"; + public const string Wink2 = "ウィンク2"; + public const string Wink2Right = "ウィンク2右"; + public const string Smile = "笑い"; + public const string Wink = "ウィンク"; + public const string WinkRight = "ウィンク右"; + } + + #endregion } } \ No newline at end of file diff --git a/README.md b/README.md index dc12bf4..553823d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For export **camera** and **morph** animations from VMD.
If you want to convert PMX to FBX, you can try using [MMD4Mecanim](https://stereoarts.jp/)
-> For ease of use, directly copied from the following project and put it in a menu, +> For ease of use, directly copied from the following project, put it in a menu and made some modifications. Therefore, please refer to the LICENSE of the original projects

[MMD2UnityTool](https://github.com/MorphoDiana/MMD2UnityTool)
[MMD4UnityTools](https://github.com/ShiinaRinne/MMD4UnityTools) @@ -21,23 +21,33 @@ Therefore, please refer to the LICENSE of the original projects

## Usage -- **For camera animation**, just right click on VMD file and select `MMD/Create Camera Anim`
- Based on the structure of anim, you need to create two empty objects in the scene as follows, - and rename the middle object to `Distance.` - +### For camera animation + just right click on VMD file and select `MMD/Create Camera Anim` + Based on the structure of anim, you need to make the camera conform to this structure
![](pic/struct.png) + #### Method1 + 1. Create two empty objects in the scene and set their parent + 2. Add `Animator component` to the top-level object (yes, not to Camera) + 3. Rename the middle object to `Distance` + 4. Rotate "Distance" 180 degrees in the Y axis + #### Method 2 + Use the tool script to create it with just one click
+ But it will create an additional Camera, and you may need to adjust the `GenerateMMDCamera` method in `MMD6UnityTool/Editor/AnimationHelpers.cs` (after line 115) for you own needs
+ ![](pic/camera.png) + + When you use camera animation via Timeline, remember uncheck `Remove Start Offset` in Clip properties
+ ![](pic/offset.png) -- **For morph animation**, you need to select the object in the scene that contains the blendshapes of the face under the - model +### For morph animation + you need to select the object in the scene that contains the blendshapes of the face under the model (If it is generated by MMD4Mecanim, it is usually `your model/U_Char/U_Char_1`)
and select the VMD with morph (ie: multi selection)
![](pic/morph.png)
Then right click and select `MMD/Create Morph Anim` -- When you use camera animation via Timeline, please uncheck `Remove Start Offset` in Clip properties
- ![](pic/offset.png) -### [Not Important] If you want to control Post-Process Volume via Timeline, feel free to try this repo + +## [Not Important] If you want to control Post-Process Volume via Timeline, feel free to try this repo [TimelineExtensions](https://github.com/ShiinaRinne/TimelineExtensions) ## License