2025-06-08 00:55:10 +09:00

318 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using MMD.VMD;
using UnityEditor;
using UnityEngine;
public class VMDCameraConverter
{
public static AnimationClip CreateAnimationClip(VMDFormat format)
{
VMDCameraConverter converter = new VMDCameraConverter();
return converter.CreateAnimationClip_(format);
}
private AnimationClip CreateAnimationClip_(VMDFormat format)
{
AnimationClip clip = new AnimationClip();
clip.name = format.name;
CreateKeysForCamera(format, clip);
return clip;
}
void CreateKeysForCamera(VMDFormat format, AnimationClip clip)
{
const float tick_time = 1f / 30f;
const float mmd4unity_unit = 0.085f;
Keyframe[] posX_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] posY_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] posZ_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] rotX_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] rotY_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] rotZ_keyframes = new Keyframe[format.camera_list.camera_count];
Keyframe[] fov_keyframes = new Keyframe[format.camera_list.camera_count];
//模拟一个相机的变换,用矩阵变换也可以,从世界坐标转局部坐标会很麻烦
var cameraWorldObj = new GameObject();
var cameraLocalObj = new GameObject();
var cameraWorldTrans = cameraWorldObj.transform;
var cameraLocalTrans = cameraLocalObj.transform;
cameraLocalTrans.SetParent(cameraWorldTrans);
for (int i = 0; i < format.camera_list.camera_count; i++)
{
VMDFormat.CameraData cameraData = format.camera_list.camera[i];
//位置
cameraWorldTrans.localPosition = new Vector3(
cameraData.location.x * mmd4unity_unit,
cameraData.location.y * mmd4unity_unit,
cameraData.location.z * mmd4unity_unit);
//旋转
//localEulerAngles取值后将角度设置为绝对值,
//https://docs.unity3d.com/cn/2017.4/ScriptReference/Transform-localEulerAngles.html
Vector3 rotationAsix = new Vector3(
-(cameraData.rotation.x) * Mathf.Rad2Deg,
-(cameraData.rotation.y) * Mathf.Rad2Deg,
-(cameraData.rotation.z) * Mathf.Rad2Deg);
cameraWorldTrans.localEulerAngles = rotationAsix;
//距离,修改局部坐标的Z值
cameraLocalTrans.localEulerAngles = Vector3.zero;
cameraLocalTrans.localPosition = new Vector3(0, 0, (cameraData.length) * mmd4unity_unit);//Z相反轴,但这里实际值已经经过取反处理
float frameTime = cameraData.frame_no * tick_time;
posX_keyframes[i] = new Keyframe(frameTime, cameraLocalTrans.position.x);
posY_keyframes[i] = new Keyframe(frameTime, cameraLocalTrans.position.y);
posZ_keyframes[i] = new Keyframe(frameTime, cameraLocalTrans.position.z);
//做动画时最好用原值,localEulerAngles取值后将角度设置为绝对值,在做补间曲线会出问题
rotX_keyframes[i] = new Keyframe(frameTime, rotationAsix.x);
rotY_keyframes[i] = new Keyframe(frameTime, rotationAsix.y);
rotZ_keyframes[i] = new Keyframe(frameTime, rotationAsix.z);
//视角fov
fov_keyframes[i] = new Keyframe(frameTime, cameraData.viewing_angle);
}
UnityEngine.Object.DestroyImmediate(cameraWorldObj);
//NOTE:这里"距离"已经与position融合了,所以没法做补间
AnimationCurve posX_curve = ToAnimationCurveWithTangentMode(1, AnimationUtility.TangentMode.Free, posX_keyframes, format.camera_list);
AnimationCurve posY_curve = ToAnimationCurveWithTangentMode(2, AnimationUtility.TangentMode.Free, posY_keyframes, format.camera_list);
AnimationCurve posZ_curve = ToAnimationCurveWithTangentMode(3, AnimationUtility.TangentMode.Free, posZ_keyframes, format.camera_list);
AnimationCurve rotX_curve = ToAnimationCurveWithTangentMode(4, AnimationUtility.TangentMode.Free, rotX_keyframes, format.camera_list);
AnimationCurve rotY_curve = ToAnimationCurveWithTangentMode(4, AnimationUtility.TangentMode.Free, rotY_keyframes, format.camera_list);
AnimationCurve rotZ_curve = ToAnimationCurveWithTangentMode(4, AnimationUtility.TangentMode.Free, rotZ_keyframes, format.camera_list);
AnimationCurve fov_curve = ToAnimationCurveWithTangentMode(6, AnimationUtility.TangentMode.Free, fov_keyframes, format.camera_list);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "m_LocalPosition.x"), posX_curve);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "m_LocalPosition.y"), posY_curve);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "m_LocalPosition.z"), posZ_curve);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "localEulerAnglesRaw.x"), rotX_curve); //采用欧拉角插值方式
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "localEulerAnglesRaw.y"), rotY_curve);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Transform), "localEulerAnglesRaw.z"), rotZ_curve);
AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve("", typeof(Camera), "field of view"), fov_curve);
}
//经过观察得知interpolation前四位分别是(x1,x2)(y1,y2)
//总共6*4点,分别是[X轴,Y轴,Z轴,旋转,距离,视角]
//都是以左下角为原点
Tuple<Vector2, Vector2> GetInterpolationPoints(byte[] interpolation, int type)
{
int row = (type - 1) * 4;
Vector2 p1 = new Vector2(interpolation[row + 0], interpolation[row + 2]);
Vector2 p2 = new Vector2(interpolation[row + 1], interpolation[row + 3]);
return new Tuple<Vector2, Vector2>(p1, p2);
}
//把MMD补间曲线中p1,p2的点映射回Curve中去
Tuple<Vector2, Vector2> ConvertToFramekeyControllerPoint(Vector2 p1, Vector2 p2, Keyframe outKeyframe, Keyframe inKeyframe)
{
var dX = inKeyframe.time - outKeyframe.time;
var dY = inKeyframe.value - outKeyframe.value;
var newP1 = new Vector2(outKeyframe.time + p1.x / 127 * dX, outKeyframe.value + p1.y / 127 * dY);
var newP2 = new Vector2(outKeyframe.time + p2.x / 127 * dX, outKeyframe.value + p2.y / 127 * dY);
//因为不存在90度的情况,这里要趋近,但是也不能太趋近,不然补间前几帧会变的陡峭
if (Mathf.Approximately(outKeyframe.time, newP1.x)) newP1.x += 0.1f;
if (Mathf.Approximately(inKeyframe.time, newP2.x)) newP2.x -= 0.1f;
return new Tuple<Vector2, Vector2>(newP1, newP2);
}
private static float Tangent(in Vector2 from, in Vector2 to)
{
Vector2 vec = to - from;
return vec.y / vec.x;
}
private static float Weight(in Vector2 from, in Vector2 to, float length)
{
return (to.x - from.x) / length;
}
//根据四个控制点计算三次贝塞尔曲线的系数
//插值:贝塞尔曲线插值(四个点)
//https://blog.csdn.net/seizeF/article/details/96368503
//return {outTangent,outWeight,inTangent,inWeight)
float[] CalculateBezierCoefficient(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float p30Length = (p3.x - p0.x);
float outTangent = Tangent(p0, p1);
float outWeight = Weight(p0, p1, p30Length);
float inTangent = Tangent(p2, p3);
float inWeight = Weight(p2, p3, p30Length);
return new float[] { outTangent, outWeight, inTangent, inWeight };
}
void SetKeyfreamTweenCurve(int index, int type, Keyframe[] keyframes, VMDFormat.CameraList cameraList)
{
if (index <= 0)
return;
VMDFormat.CameraData curCameraData = cameraList.camera[index];
VMDFormat.CameraData lastCameraData = cameraList.camera[index - 1];
Keyframe outKeyframe = keyframes[index - 1];
Keyframe inKeyframe = keyframes[index];
var dX = inKeyframe.time - outKeyframe.time;
var dY = inKeyframe.value - outKeyframe.value;
outKeyframe.weightedMode = WeightedMode.Both;
inKeyframe.weightedMode = WeightedMode.Both;
if (Mathf.Approximately(dY, 0f) || Mathf.Approximately(dX, 1 / 30f)) //没有变化的就不需要补间插值了
{
outKeyframe.outTangent = 0;
outKeyframe.outWeight = 0;
inKeyframe.inTangent = 0;
inKeyframe.inWeight = 0;
}
else
{
//插值计算[0~127]
//参考https://www.jianshu.com/p/ae312fb53fc3
Vector2 p0 = new Vector2(outKeyframe.time, outKeyframe.value);
Vector2 p3 = new Vector2(inKeyframe.time, inKeyframe.value);
Vector2 p1 = Vector2.zero;
Vector2 p2 = Vector2.zero;
var intTuple = GetInterpolationPoints(curCameraData.interpolation, type);
var ptTuple = ConvertToFramekeyControllerPoint(intTuple.Item1, intTuple.Item2, outKeyframe, inKeyframe); //转化为keyFrame的控制点
p1 = ptTuple.Item1;
p2 = ptTuple.Item2;
float[] coeffs = CalculateBezierCoefficient(p0, p1, p2, p3);
outKeyframe.outTangent = coeffs[0];
outKeyframe.outWeight = coeffs[1];
inKeyframe.inTangent = coeffs[2];
inKeyframe.inWeight = coeffs[3];
}
//因为是结构体,所以需要重新赋值
keyframes[index - 1] = outKeyframe;
keyframes[index] = inKeyframe;
}
AnimationCurve ToAnimationCurveWithTangentMode(int type, AnimationUtility.TangentMode mode, Keyframe[] keyframes, VMDFormat.CameraList cameraList)
{
if (mode == AnimationUtility.TangentMode.Free)
{
for (int i = 0; i < keyframes.Length; i++)
{
SetKeyfreamTweenCurve(i, type, keyframes, cameraList);
}
}
var newKeyFrames = OptimizedCurves(type, keyframes, cameraList);
AnimationCurve curve = new AnimationCurve(newKeyFrames);
for (int i = 0; i < curve.keys.Length; i++)
{
if (mode == AnimationUtility.TangentMode.Free)
AnimationUtility.SetKeyBroken(curve, i, true);
AnimationUtility.SetKeyLeftTangentMode(curve, i, mode);
AnimationUtility.SetKeyRightTangentMode(curve, i, mode);
}
return curve;
}
////由于Unity自带的插值曲线平滑效果不是很好,可以通过插入关键帧的方式平滑曲线
//三次贝塞尔曲线插值
Vector2 EvaluteThire(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
return Mathf.Pow(1 - t, 3) * p0 + 3 * Mathf.Pow(1 - t, 2) * t * p1 + 3 * (1 - t) * Mathf.Pow(t, 2) * p2 + Mathf.Pow(t, 3) * p3;
}
//补间插值插入关键帧
List<Keyframe> CalculateInterpolationKeyframe(int type, byte[] interpolation, Keyframe outKeyframe, Keyframe inKeyframe, float density = 0.05f)
{
density = Mathf.Min(1f, density);
density = Mathf.Max(0f, density);
var p0 = new Vector2(outKeyframe.time, outKeyframe.value);
var p3 = new Vector2(inKeyframe.time, inKeyframe.value);
var intTuple = GetInterpolationPoints(interpolation, type);
var ptTuple = ConvertToFramekeyControllerPoint(intTuple.Item1, intTuple.Item2, outKeyframe, inKeyframe); //转化为keyFrame的控制点
var p1 = ptTuple.Item1;
var p2 = ptTuple.Item2;
var dX = inKeyframe.time - outKeyframe.time;
var dY = inKeyframe.value - outKeyframe.value;
float stepTime = (1 / 30f) / density;
List<Keyframe> interpolationKeyframes = new List<Keyframe>();
if (!Mathf.Approximately(dY, 0f))
{
if (stepTime > 0f && stepTime >= (1 / 30f))
{
for (float time = outKeyframe.time + stepTime; time < inKeyframe.time; time += stepTime)
{
float t = (time - outKeyframe.time) / dX;
var pt = EvaluteThire(t, p0, p1, p2, p3);
var newKeyframe = new Keyframe(pt.x, pt.y);
interpolationKeyframes.Add(newKeyframe);
}
}
}
return interpolationKeyframes;
}
Keyframe[] OptimizedCurves(int type, Keyframe[] keyframes, VMDFormat.CameraList cameraList)
{
List<Keyframe> framesList = new List<Keyframe>();
//曲线优化
for (int i = 0; i < keyframes.Length; i++)
{
//if (i > 0)
//{
// var lastKeyFrame = keyframes[i - 1];
// var curKeyFrame = keyframes[i];
// VMDFormat.CameraData curCameraData = cameraList.camera[i];
// var exKeyFrames = CalculateInterpolationKeyframe(type, curCameraData.interpolation, lastKeyFrame, curKeyFrame);
// framesList.AddRange(exKeyFrames);
//}
var keyframe = keyframes[i];
framesList.Add(keyframe);
}
//针对只有一帧的进行优化
if (framesList.Count == 1)
{
Keyframe[] newKeyframes = new Keyframe[2];
newKeyframes[0] = keyframes[0];
newKeyframes[1] = keyframes[0];
newKeyframes[1].time += 0.001f / 60f;//1[ms]
newKeyframes[0].outTangent = 0f;
newKeyframes[1].inTangent = 0f;
framesList.Clear();
framesList.AddRange(newKeyframes);
}
return framesList.ToArray();
}
}