Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
ShiinaRinne committed Aug 30, 2023
1 parent 3827f25 commit a5d7346
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 107 deletions.
232 changes: 135 additions & 97 deletions Editor/AnimationHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DefaultAsset>(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<DefaultAsset>(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<GameObject>(SelectionMode.TopLevel).FirstOrDefault();
var mesh = gameobject.GetComponent<SkinnedMeshRenderer>().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<GameObject>(SelectionMode.TopLevel).FirstOrDefault();
var mesh = gameobject.GetComponent<SkinnedMeshRenderer>().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<string> 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<Animator>();
camera.AddComponent<Camera>();
camera.AddComponent<AudioListener>();

// Just a reference, you can change this part according to your own needs

// =========== Enable postprocessing for unity urp ===========
// var uac = camera.gameObject.AddComponent<UniversalAdditionalCameraData>();
// uac.renderPostProcessing = true;
// uac.antialiasing = AntialiasingMode.SubpixelMorphologicalAntiAliasing;
// uac.antialiasingQuality = AntialiasingQuality.High;
}

#region Utils

private static void AddCurveToAnimationClip(AnimationClip animationClip, string parentName,
string gameObjectName, List<string> 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
}
}
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ For export **camera** and **morph** animations from VMD.
<br>If you want to convert PMX to FBX, you can try using [MMD4Mecanim](https://stereoarts.jp/)
<br>

> 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<br><br>
[MMD2UnityTool](https://github.com/MorphoDiana/MMD2UnityTool)<br>
[MMD4UnityTools](https://github.com/ShiinaRinne/MMD4UnityTools)
Expand All @@ -21,23 +21,33 @@ Therefore, please refer to the LICENSE of the original projects<br><br>

## Usage

- **For camera animation**, just right click on VMD file and select `MMD/Create Camera Anim`<br>
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<br>
![](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 <br>
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 <br>
![](pic/camera.png)

When you use camera animation via Timeline, remember uncheck `Remove Start Offset` in Clip properties<br>
![](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`)<br>
and select the VMD with morph (ie: multi selection)<br>
![](pic/morph.png) <br>
Then right click and select `MMD/Create Morph Anim`

- When you use camera animation via Timeline, please uncheck `Remove Start Offset` in Clip properties<br>
![](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
Expand Down

0 comments on commit a5d7346

Please sign in to comment.