Unity AR Foundation の FaceTrackingを使ってバ美肉してみる



「[Applibot Advent Calendar 2021](https://qiita.com/advent-calendar/2021/applibot)」 19日目の記事になります。   
前日は 「[2021 年の Odin Inspector 事情](https://zenn.dev/tenonno/articles/81ab92a339f6f9) 」という記事でした、Odinおしゃれだぁ

# :movie_camera: できたもの
[ブレイドエクスロード](https://bladexlord.jp/)に登場するキャラクター、[アミナ・ガーベル](https://bladexlord.jp/character/detail/?id=4363)のモデルを利用し、FaceTrackingで表情を変化させてみました。

![trim3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/2a63eedf-ed0b-1f17-4dad-4681c38e1e2a.gif)



※ 2021年の中頃まで弊社で運営、その後、[株式会社マイネット](https://mynet.co.jp/)様に運営移管しております。   
※ マイネット様より、掲載の許可は頂いております。

## なぜやったのか
当時（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](https://unity.com/ja/unity/features/arfoundation)
Unity公式のAR開発用のフレームワーク、PackageManagerからインストールできる。   
今回はFaceTrackingのみしか使いませんが、いろんな事ができます。   
- 平面検知（Plane Detection）
- 画像追跡（Image Tracking）
- 物体追跡（Object Tracking）
- 表情追跡（Face Tracking）
- etc …   

[こちら](https://github.com/Unity-Technologies/arfoundation-samples)の Unity-TechnologiesさんのGitHubにいろんなサンプルがあります。

Android/iOSともに利用可能、FaceTrackingに関してはiPhone X以降からの、FaceID搭載の端末であればより高精度に取れるっぽい？

### 1. サンプルシーンを実機で動かす
[先程のGutHub](https://github.com/Unity-Technologies/arfoundation-samples)からサンプルシーンを持ってきて、プラットフォームをiOSにしてXCodeプロジェクトを書き出し->実機でFaceTrackingができていることを確認してみる。

#### こうなる

FaceTrackingのシーンまで遷移するとこうなるはず。ナマケモノのモデルが自身の顔の上に表示され、顔の向きや口、瞬きなどがトレースされています。

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/8ac5d866-0616-2ad0-7999-3b01b80affa9.gif)


アタッチされているコンポーネントの処理を覗いてみると、`SlothHead`プレハブについている`ARFace` コンポーネントで顔の位置を同期して、`ARKitBlendShapeVisualizer`で表情のキーとパラメータを`BlendShape`の値に流してるらしい、なるほど。

```CSharp:ARKitBlendShapeVisualizer.cs

// 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
}
```  

```CSharp:ARKitBlendShapeVisualizer.cs
// 紐付け情報をもとにブレンドシェイプの値を更新している
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
}
```


#### :beginner: BlendShape
[CGWorldさんの記事](https://entry.cgworld.jp/terms/%E3%83%96%E3%83%AC%E3%83%B3%E3%83%89%E3%82%B7%E3%82%A7%E3%82%A4%E3%83%97.html)がわかりやすいです。   
Unityでは `SkinnedMeshRenderer` のインスペクター上に表示されています。今回のFaceTrackingではこの値を調整することで表情を変化させています。

![skin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/98e46d2a-cbbb-6e22-e272-3952b2f0da27.png)



### 2. サンプルシーンのモデルを置き換えてBlendShapeを反映してみる。

ひとまず、ナマケモノのモデルを、今回使うモデルに置き換えていく。   
`ARFace`を`ARKitBlendShapeVisualizer`をモデルにアタッチ、`ARKitBlendShapeVisualizer`の `SkinnedMeshRenderer`をアタッチする。


#### ARKitBlendShapeLocation と ブレンドシェイプの名前を紐付ける
BlendShapeの命名はモデルによって違うので、今回は中間の紐付けを行う `ScriptableObject`を作って値を流し込んでみました。

```CSharp:ARKitBlendShapeVisualizer.cs
[SerializeField]
private ARFaceBlendShapeBindData _data = null;

void CreateFeatureBlendMapping()
{
    // キーとブレンドシェイプ名を流し込む
    foreach (var bindInfo in _data.BindInfos)
    {
        m_FaceArkitBlendShapeIndexMap[bindInfo.Key] = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(_data.Prefix + bindInfo.Name);
    }
}
```

```CSharp:ARFaceBlendShapeBindData.cs
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を含めている状態。

```CSharp:ARFaceBody.cs
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` にチェックを入れて、緑一色のマテリアルをアタッチする。


#### :movie_camera: こうなった

![face.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/0f603ddc-d490-4e0a-c673-d9e85eb29f6e.gif)

### 3. 目、揺れものを対応していく

#### まずは目
ナマケモノの目は、瞬きはしているものの、眼球自体は動いていませんでしたが、`ARFace`には `rightEye` `leftEye` のプロパティが公開されているので、 `ARFaceEye`コンポーネントを作り、毎フレームモデルの目のトランスフォームに同期させることで眼球を回転させることができました。

```CSharp:ARFaceEye.cs
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](https://assetstore.unity.com/packages/tools/animation/dynamic-bone-16743?locale=ja-JP)を購入して各所に設定した。   
Dynamic Boneの設定方法に関しては[こちら](https://noshilog.com/unity-dynamic-bone)が非常にわかりやすかったです。  

一応、髪の毛のパラメータはこんな感じです。

![param.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/b4327b43-cc06-f029-0286-77acadb1c347.png)


#### :movie_camera: こうなった

![comp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/6062ee7c-6523-734e-cc87-c0a2a88fcf4c.gif)


完成！

### おまけ

[株式会社miHoYo](http://corp.mihoyo.co.jp/)様より公式で配布されている原神のキャラクターのMMDを[MMD4Mecanim](https://stereoarts.jp/)を介して同じことをしてみた。いざ使おうとするといろんなところで引っかかるので困ったら最近投稿された[こちらの記事](https://qiita.com/ishi0/items/e1b58ded4328184e6d59)がおすすめ。

![mhy.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20650/7cd2103f-3a05-8978-823e-86d60643997e.gif)


基本的な作りは一緒なので、`ARFaceBlendShapeBindData`でマッピング情報を変えるだけで動いたりします。

終わり

### 参考リンクまとめ
- [ブレイドエクスロード](https://bladexlord.jp/)
  - [株式会社マイネット](https://mynet.co.jp/)
- [Unity AR Foundation](https://unity.com/ja/unity/features/arfoundation)
  - [サンプルGitHub](https://github.com/Unity-Technologies/arfoundation-samples)
  - [BlendShape](https://entry.cgworld.jp/terms/%E3%83%96%E3%83%AC%E3%83%B3%E3%83%89%E3%82%B7%E3%82%A7%E3%82%A4%E3%83%97.html)
  - [Dynamic Bone](https://assetstore.unity.com/packages/tools/animation/dynamic-bone-16743?locale=ja-JP)
    - [Dynamic Bone説明](https://noshilog.com/unity-dynamic-bone)
- [株式会社miHoYo](http://corp.mihoyo.co.jp/)
- [MMD4Mecanim](https://stereoarts.jp/)
  - [使い方に困ったら](https://qiita.com/ishi0/items/e1b58ded4328184e6d59)

※この記事の内容は個人の意見であり、所属団体の意見ではありません。