「Applibot Advent Calendar 2021」 19日目の記事になります。
前日は 「2021 年の Odin Inspector 事情 」という記事でした、Odinおしゃれだぁ
できたもの
ブレイドエクスロードに登場するキャラクター、アミナ・ガーベルのモデルを利用し、FaceTrackingで表情を変化させてみました。
※ 2021年の中頃まで弊社で運営、その後、株式会社マイネット様に運営移管しております。
※ マイネット様より、掲載の許可は頂いております。
なぜやったのか
当時(2020年頭)、リモートワークが増えてzoomでのMTGが増えた。当時ちょうど3D周りを少し触っていた。そして単純にVTuberが好きだった。
やってみる
環境
version | ||
---|---|---|
Unity | 2019.3.12f1 | |
PackageManager | Unity AR Foundation | 4.1.0 |
ARCore XR Plugin | 4.1.0 | |
ARKit Face Tracking | 4.1.0 | |
ARKit XR Plugin | 4.1.0 |
Unity AR Foundation
Unity公式のAR開発用のフレームワーク、PackageManagerからインストールできる。
今回はFaceTrackingのみしか使いませんが、いろんな事ができます。
- 平面検知(Plane Detection)
- 画像追跡(Image Tracking)
- 物体追跡(Object Tracking)
- 表情追跡(Face Tracking)
- etc …
こちらの Unity-TechnologiesさんのGitHubにいろんなサンプルがあります。
Android/iOSともに利用可能、FaceTrackingに関してはiPhone X以降からの、FaceID搭載の端末であればより高精度に取れるっぽい?
1. サンプルシーンを実機で動かす
先程のGutHubからサンプルシーンを持ってきて、プラットフォームをiOSにしてXCodeプロジェクトを書き出し->実機でFaceTrackingができていることを確認してみる。
こうなる
FaceTrackingのシーンまで遷移するとこうなるはず。ナマケモノのモデルが自身の顔の上に表示され、顔の向きや口、瞬きなどがトレースされています。
アタッチされているコンポーネントの処理を覗いてみると、SlothHead
プレハブについているARFace
コンポーネントで顔の位置を同期して、ARKitBlendShapeVisualizer
で表情のキーとパラメータをBlendShape
の値に流してるらしい、なるほど。
// ARKitBlendShapeLocation と ブレンドシェイプの名前を紐付けている
void CreateFeatureBlendMapping()
{
if (skinnedMeshRenderer == null || skinnedMeshRenderer.sharedMesh == null)
{
return;
}
#if UNITY_IOS && !UNITY_EDITOR
const string strPrefix = "blendShape2.";
m_FaceArkitBlendShapeIndexMap = new Dictionary<ARKitBlendShapeLocation, int>();
m_FaceArkitBlendShapeIndexMap[ARKitBlendShapeLocation.BrowDownLeft ] = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browDown_L");
m_FaceArkitBlendShapeIndexMap[ARKitBlendShapeLocation.BrowDownRight ] = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browDown_R");
// ~ 省略 ~
m_FaceArkitBlendShapeIndexMap[ARKitBlendShapeLocation.TongueOut ] = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "tongueOut");
#endif
}
// 紐付け情報をもとにブレンドシェイプの値を更新している
void UpdateFaceFeatures()
{
if (skinnedMeshRenderer == null || !skinnedMeshRenderer.enabled || skinnedMeshRenderer.sharedMesh == null)
{
return;
}
#if UNITY_IOS && !UNITY_EDITOR
using (var blendShapes = m_ARKitFaceSubsystem.GetBlendShapeCoefficients(m_Face.trackableId, Allocator.Temp))
{
foreach (var featureCoefficient in blendShapes)
{
int mappedBlendShapeIndex;
if (m_FaceArkitBlendShapeIndexMap.TryGetValue(featureCoefficient.blendShapeLocation, out mappedBlendShapeIndex))
{
if (mappedBlendShapeIndex >= 0)
{
skinnedMeshRenderer.SetBlendShapeWeight(mappedBlendShapeIndex, featureCoefficient.coefficient * coefficientScale);
}
}
}
}
#endif
}
BlendShape
CGWorldさんの記事がわかりやすいです。
Unityでは SkinnedMeshRenderer
のインスペクター上に表示されています。今回のFaceTrackingではこの値を調整することで表情を変化させています。
2. サンプルシーンのモデルを置き換えてBlendShapeを反映してみる。
ひとまず、ナマケモノのモデルを、今回使うモデルに置き換えていく。
ARFace
をARKitBlendShapeVisualizer
をモデルにアタッチ、ARKitBlendShapeVisualizer
の SkinnedMeshRenderer
をアタッチする。
ARKitBlendShapeLocation と ブレンドシェイプの名前を紐付ける
BlendShapeの命名はモデルによって違うので、今回は中間の紐付けを行う ScriptableObject
を作って値を流し込んでみました。
[SerializeField]
private ARFaceBlendShapeBindData _data = null;
void CreateFeatureBlendMapping()
{
// キーとブレンドシェイプ名を流し込む
foreach (var bindInfo in _data.BindInfos)
{
m_FaceArkitBlendShapeIndexMap[bindInfo.Key] = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(_data.Prefix + bindInfo.Name);
}
}
using UnityEngine;
using UnityEngine.XR.ARKit;
[CreateAssetMenu(menuName = "Data/ARFaceBlendShapeBindData", fileName = "dat_blend_shape_bind" )]
public class ARFaceBlendShapeBindData : ScriptableObject
{
[System.Serializable]
public class BindInfo
{
public ARKitBlendShapeLocation Key;
public string Name;
}
public string Prefix;
public BindInfo[] BindInfos;
}
体の動き
ナマケモノは顔だけでしたが、アミナには身体があるので、顔~首と、体の部分のゲームオブジェクトを分離させ、体の方のゲームオブジェクトを首の根元を起点に顔の逆方向に回転させてみた。
ARFaceBody
を ARFace
と同じオブジェクトにアタッチ、その子階層に体のTransformを含めている状態。
using UnityEngine;
using UnityEngine.XR.ARFoundation.Samples;
using UnityEngine;
public class ARFaceBody : MonoBehaviour
{
[SerializeField] private Transform _body = null;
void Update()
{
if (_body != null) {
// オブジェクトの階層構造に寄ってはうまくいません
_body.localRotation = transform.localRotation;
}
}
}
ついでにグリーンバックにする
背景がカメラの映像そのまま出るようになっているので、メインカメラについているAR Camera Background
の Use Custom Material
にチェックを入れて、緑一色のマテリアルをアタッチする。
こうなった
3. 目、揺れものを対応していく
まずは目
ナマケモノの目は、瞬きはしているものの、眼球自体は動いていませんでしたが、ARFace
には rightEye
leftEye
のプロパティが公開されているので、 ARFaceEye
コンポーネントを作り、毎フレームモデルの目のトランスフォームに同期させることで眼球を回転させることができました。
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class ARFaceEye : MonoBehaviour
{
[SerializeField] private ARFace _face;
[SerializeField] private Transform _right;
[SerializeField] private Transform _left;
void Update()
{
if (_face.rightEye != null) {
var angle = _face.rightEye.transform.localRotation.eulerAngles;
_right.transform.localRotation = Quaternion.Euler( angle.x, angle.y, angle.z);
}
if (_face.leftEye != null) {
var angle = _face.leftEye.transform.localRotation.eulerAngles;
_left.transform.localRotation = Quaternion.Euler( angle.x, angle.y, angle.z);
}
}
}
揺れもの
Dynamic Boneを購入して各所に設定した。
Dynamic Boneの設定方法に関してはこちらが非常にわかりやすかったです。
一応、髪の毛のパラメータはこんな感じです。
こうなった
完成!
おまけ
株式会社miHoYo様より公式で配布されている原神のキャラクターのMMDをMMD4Mecanimを介して同じことをしてみた。いざ使おうとするといろんなところで引っかかるので困ったら最近投稿されたこちらの記事がおすすめ。
基本的な作りは一緒なので、ARFaceBlendShapeBindData
でマッピング情報を変えるだけで動いたりします。
終わり
参考リンクまとめ
※この記事の内容は個人の意見であり、所属団体の意見ではありません。