前書き
MediaPipeを勉強がてらVrmの表情操作を行えるようにしてみようと思って作りました
顔編と題しているものの、体編など作るかは未定です。。。(期待してたかたすいませんmm
また、コードの全文は記載しません
FaceMeshの頂点データの番号は記載します(調べるの面倒だったので少しでも助けになれば
完成品
右上が自分の顔の動きです
ちなみに、細かい部分の動きはかなり誤魔化してます
使用技術
・Unity2022.3.20f1
・vrm1.0
・https://github.com/homuler/MediaPipeUnityPlugin
・UniTask
苦戦したこと
・頂点のIndex調べる
・vrmのパラメーターにfaceMeshの値を補正する
正直このぐらいでした
それ以外はすんなりできた
参考資料
↓の記事を参考に補正を作ってみたり
https://note.com/reality_eng/n/ndaddbaf3aaae
https://note.com/reality_eng/n/nb36f85b8123e
↓の記事から顔の向きを作ってみたり
https://zenn.dev/ykesamaru/articles/025b0eabf24e20
コード
頂点のIndex(2024/04/27時点
public static class FaceLandmarkIndex
{
// 鼻の頭
public const int NoseTop = 1;
// 唇
public const int UpperLipBottom = 13;
public const int LowerLipTop = 14;
public const int UpperLipTop = 0;
public const int LowerLipBottom = 17;
//口角
public const int MouthCornerRight = 61;
public const int MouthCornerLeft = 291;
//輪郭
public const int ContourBottom = 152;
public const int ContourRight = 93;
public const int ContourLeft = 366;
public const int ContourTop = 10;
//まぶた
public const int LeftEyelidLower = 374;
public const int LeftEyelidUpper = 386;
public const int RightEyelidUpper = 159;
public const int RightEyelidLower = 144;
//眉
public const int LeftBrowCenterUpper = 336;
public const int LeftBrowCenterLower = 285;
public const int LeftBrowOuterUpper = 293;
public const int LeftBrowOuterLower = 283;
public const int RightBrowCenterUpper = 107;
public const int RightBrowCenterLower = 55;
public const int RightBrowOuterUpper = 63;
public const int RightBrowOuterLower = 53;
//目
public const int RightEye = 469;
public const int LeftEye = 474;
}
全体のコード
public class VrmFace : MonoBehaviour
{
[SerializeField] private Vrm10Instance vrmInstance;
/* 補正値などを入れておくと便利 */
private Vrm10RuntimeExpression vrm10RuntimeExpression;
//
private bool isInitialized = false;
private bool isDestroyed = false;
private NormalizedLandmarkList defaultFaceLandmarks;
private NormalizedLandmarkList tempFaceLandmarks;
private Quaternion neckDefaultRotation;
private Quaternion chestDefaultRotation;
private void Start()
{
//初期化処理を登録
initializeButton.onClick.AddListener(InitializeFace);
//VRMの表情を取得
vrm10RuntimeExpression = vrmInstance.Runtime.Expression;
//初期化
/* 補正値 */
}
private void OnDestroy()
{
isDestroyed = true;
}
public void InitializeFace()
{
//デフォルトの表情を取得
defaultFaceLandmarks = tempFaceLandmarks.Clone();
isInitialized = true;
}
//自分でカスタマイズしたGraphRunnerとImageSourceSolutionから呼び出している
public async UniTaskVoid SyncFace(NormalizedLandmarkList resultFaceLandmarksWithIris)
{
tempFaceLandmarks = resultFaceLandmarksWithIris;
if (!isInitialized) return;
// mediaPipe側でコールバックで呼び出される際はMainThreadにいない時があるため戻しておく
await UniTask.SwitchToMainThread();
if(this == null || isDestroyed) return;
//表情の変更処理
if (tempFaceLandmarks?.Landmark == null) return;
var lipLength = GetPureFaceLandmarkDistance(FaceLandmarkIndex.UpperLipBottom, FaceLandmarkIndex.LowerLipTop);
var mouseWidth = GetPureFaceLandmarkDistance(FaceLandmarkIndex.MouthCornerRight, FaceLandmarkIndex.MouthCornerLeft);
var leftEyelidLength = GetPureFaceLandmarkDistance(FaceLandmarkIndex.LeftEyelidLower, FaceLandmarkIndex.LeftEyelidUpper);
var rightEyelidLength = GetPureFaceLandmarkDistance(FaceLandmarkIndex.RightEyelidLower, FaceLandmarkIndex.RightEyelidUpper);
/*非公開 口、口角、瞬きの処理*/
/* ここから目の位置を変更する処理
むずいのでこのままだと動かないためコメントアウト(代用としてLookAtを使用する
/*非公開 目のトラッキング、調整がむずいため断念*/
*/
//顔の回転
/*非公開*/
//体の向き
/*非公開*/
debugText.text = $"Lip: {lipValue}\n" +
$"Mouth: {mouthValue}\n" +
$"Blink: {leftBlinkValue} {rightBlinkValue}\n" +
/*$"Eye: {leftEyeMovement} {rightEyeMovement}"*/
$"Angle: {angleY} {angleX}";
}
private (float, float) GetPureMovableVector(int index)
{
/*非公開*/
}
private float GetPureFaceLandmarkDistance(int index1, int index2)
{
/*非公開 各Landmarkの座標の距離などを計算する関数*/
}
private float GetValue(float current, float higher, float defaultValue)
{
/*非公開 瞬きや口の補正値関数*/
}
}