【Unity】iPhoneのAR KitでFace Trackingし、Vroid Hubの3Dアバターを動かすハンズオン


# はじめに
　私は現在、趣味でAR Kitを使ったアプリを開発しています。以下のスクリーンショットにもあるように、1対１で3Dアバターをお互いに使用して、通話をすることができるアプリになっています。まだApple Storeにはリリースしてはいないのですが、近いうちにリリースして機能の追加などを行なっていきたいと考えているので、見つけた際にはダウンロードしてご意見をいただけると幸いです。
　この記事では、私がこのアプリを作る上で苦労した部分の一つである、 __iPhoneでFace Trackingを行い、3Dアバターを実際に動かすという部分__ をざっくりとですがハンズオン形式で解説していき、最終的にこの動画のようなアプリを作成したいと思います。この部分の実装方法に関しては記事も少なく、私自身苦労したので、同じように悩んでいる誰かの参考になれば良いなと期待しています。また、ARに興味があってこういうことをやってみたかったという人にも理解できる記事になるようにと考えています。
　また、私自身Unityを始めてから１年未満の初心者なので、文法が間違っていたりコードが汚いなどがあると思いますが、多めに見ていただけるとありがたいです。
<img src="https://00f74ba44b59119a1b721b87621c23016ed04fe033-apidata.googleusercontent.com/download/storage/v1/b/virtual-chat-19fb0/o/sample3.gif?jk=AFshE3WWinbpgytXLJzd22uTZFMPXXzxL7DKib-wEBqp_5eeRXabYgoPwKYI1adgK9aHVSfV3z-3eAGLQ0QbMlxAp-yUJvSEWToPkgAqZEgtChmZIS_7NBUVWlmX0G5SPKrQs0tjvYTMTB50Sz1Ywm_cq8-vTJpyMYGoj9yINVnHF4n0MpGhk8WUJyalB0MLtML6cWQrIxoPooSYFCiZC8uCdVFbIU2Zpks2XEUuH3YKv9qD1pLR-0-mI4U2JgAvce7KOnoLFGxCevniJXcoB2kbzu2Uhvd4mpDUyZdavw3dTj5P3AgRTcPgJrBtJBXElPFUsZPVPBy8ZhStt4E50z6RGWAHpeNwXsi7-rMfGJqOsttOcJV2jUwIJMffL6QFcYs8iQHYC_10WcktH2GKA07MFBlA-WcXShsITlidZpfvS6hp1O5EwVFl85Ux4xTlImGelIEXhHUR3UbP2H0J2TeftRFPSAeQB4pHSrNe3kM6wWPo7E8MF-g58njM80IPXL4_rTaYHaNq1gNWZTJFEc1VTii7EwAM5S8ehQYFAw2Aoom2H9gzxAvdfQID3rUIS7fljBgAuIYBBS-U3xVOf7AG6GpK7Pz-CHxUNWUaPqEGuG9t7zqJXe36NP6LIxAgFIb-1jf4VmHNnR4PUGpIlnJB9djINyDpahKHB5i6WjvsIfj_tq8DO-SssAl34bEYvsyIbM54jnkVguorZpa-T5wMdB4RmmswcXXKWsh9YKJsX7aS5wxYIoayXJenHrndhDNAKMd7xu6TEQTqMnBhk90fS2sAut-Evcytx4qxt2NexUdLJFvAMBdxwoQRSGwycvgDK5uBrTfD5zdNJH7figLdofh15oabptw-usQX30upzevJ569m5d6Q_OJoW08qLUZLjKvc-057ZMS5UXth-mUV69ez0G8ouOOicbqOPwXBZr72Nhjim0bhtalvdv88TS3jN8T1FZsNedTbrnnE7NCfMpfpmN9QeO2B_3dXur0RId8Gni_WUpMopFvfdYTEBp_zVi-VkrWnWF4XInPHIlKZ_3i04xDOzXfPlP3Wp7Y19qizRc6rVADa-IMN0QwRzAhRr02pPjKjoLrmAyTGgu-u1YWE&isca=1" width="300"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/858f50ea-9f8e-e0de-0c4c-c4c5b7eacd9f.png" width="500">

## 環境
- macOS Big Sur 11.6
- Unity 2021.1.5f1
- AR Foundation, ARKit XR Plugin、ARKit Face Tracking Version 4.1.7
- UniVRM v0.91.0
- iPhone 11(実機検証用)

:::note alert
iPhone 8/8 Plus以前のiPhoneは、Face Trackingに対応していないため、動作させることはできないので注意してください
:::

## 開発の流れ
では実際にアプリを開発していきます。今回やりたいことは、主に３つあります。最初に、 __Unity上に3Dアバターを表示する__ ことを行います。その次に、 __ARKitを使ってiPhoneのカメラから自分の顔の情報(顔の位置や向き、まぶたの開き具合など)を取得する__ ということを行います。最後に、 __３Dアバターを取得した自分の顔の情報を元に動かす__ という、２つのことをやっていきたいと思います。

### 3Dアバターを表示させる
最初に今回の記事では、VRMというドワンゴが提唱して、実装したオープンソースの人型3Dモデル用ファイルフォーマットを利用します。私も詳しい部分までは理解できていませんが、VRMを利用することで3Dアバター特有の骨格の操作（顔や指を動かす等）や、表情をつけたりといったことがアプリの開発者が、簡単にできるようになるといったものになっています。さらに内容については、[公式サイト](https://vrm.dev/)などを参照してみてください。

1. プロジェクトを作成
Unity Hubを開き[New project]をクリックして、テンプレートは3Dを選択し[Create project]を押してプロジェクトを新規作成してください。

1. パッケージのインストール
新規作成ができたら、Package ManagerからUnity Registryのタブを選択し、[AR Foundation]、[ARKit XR Plugin]、[ARKit Face Tracking]をインストールしてください

1. UniVRMのインストール
次にUnity上でVRMファイルを読み書きを行うためのパッケージであるUniVRMをインストールします。[GitHubのリリースページ](https://github.com/vrm-c/UniVRM/releases)から、UniVRM-*.unitypackageをPCにダウンロードして、Unityプロジェクトを開きながらダブルクリックでプロジェクト内にインストールされます。ここまででパッケージのインストールは終わりです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/e9388234-5e9e-b120-a2e4-fc2137c07ea6.png" width="500">

1. VRM形式の3Dアバターの準備
アプリ内で表示させて動かす3Dアバターを準備します。この記事では[VRoid Hub](https://hub.vroid.com/)というサービスから無償でVRMファイルをダウンロードさせてもらいます。この記事では以下のページからモデルをダウンロードしたモデルを使用していきます。
https://hub.vroid.com/characters/2843975675147313744/models/5644550979324015604
ダウンロードしたVRMファイルをUnity上にドラックすることで、自動的にUniVRMがファイルを展開してくれてプレハブとして3Dモデルを使用できるようにしてくれます。ドラックするとファイルが沢山出てくるので、あらかじめAssets下にModelというフォルダを作成してその中で展開するようにしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/31b315d9-bb9e-78ee-3d9f-435acc158942.png" width="500">

最終的に以下の様にアバターのプレハブが出力されればいい感じです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/23c9e98b-910f-1215-6aa5-ca4e29236492.png" width="800">

1. アバターの表示
次にアバターのプレハブをシーンにドラックして表示させます。いい感じに移動させればカメラにも表示させられる様になりました。ひとまず確認ができたら、シーン上のプレハブは削除しておいてください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/583cce73-a034-a8d4-17ad-ea35028603f8.png" width="500">

### ARKitを使って自分の顔の情報を取得
1. AR Session OriginとAR Sessionの追加
ARを用いる場合には、基本的にAR Sessionコンポーネントと、AR Session Originコンポーネントが必要になります。AR Sessionは、ARのセッション全体のライフサイクルのコントロール(カメラを使ったトラッキングの開始、終了させる等)などを行なっています。AR Session Originは、現実空間の位置・方向・大きさなどの情報をUnity上で扱える様に変換する役割を行なっています。
Hierarchyの+ボタンから以下の図の様にAR Session Originを追加します。また、その下にあるAR Sessionも同様に追加しておいてください。
追加するとわかるのですが、AR Session Originの下にAR Cameraが存在します。今回使用するカメラはこのAR Cameraになるので、__元々あったMain Cameraは削除__しておいてください。さらに、AR Cameraの[AR Camera Manager]コンポーネントの[Facing Direction]をWorldからUserに変更してください。これを変更することで、iPhoneのインカメラで顔認識を行うことができる様になります。今回は自撮りをするアプリになるのでUserにしておきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/379e1e05-ef53-73d7-df12-366564a570c4.png" width="300">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/32ba0742-0618-ce7c-2efc-db41e8f2f76d.png" width="500">

1. AR Face Mangerを追加
AR Session OriginオブジェクトにAR Face Managerを追加してください。このコンポーネントを追加することで顔認識を行うことができる様になります。具体的には、検出した顔の情報の変化を購読することができる様になったり、[この動画のように](https://www.youtube.com/watch?v=VkVq8gm73SE)、Face Prefabにセットしたプレハブを表示させる(顔に何かしらのマスクをかけるなど)ことができる様になります。今回は、その様なことはせずに顔の情報だけを取得したいため、Face Prefabは設定しなくて大丈夫です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/7fdc8f94-c0e7-2af5-6b57-690e1b735c0e.png" width="500"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/d9b9fc91-0e07-c8e9-8e0c-35e2a5c012da.png" width="300">


1. 顔の情報を取得するスクリプトの作成
次に実際に顔の情報を取得する簡単なスクリプトを書いていきます。最終的にできたコードが以下の様になります。このコードを空のGameObjectを追加し、Managerなどの名前に変更して、そこに貼り付けます。__Face ManagerにAR Session OriginのAR Face Managerをセットするのを忘れない様にしてください。__

    ```C#
    using UnityEngine;
    using UnityEngine.XR.ARFoundation;
    using UnityEngine.XR.ARSubsystems;

    public class FaceTracking : MonoBehaviour
    {
        [SerializeField] private ARFaceManager faceManager;

        private void OnEnable()
        {     
            faceManager.facesChanged += OnFaceChanged;
        }

        private void OnDisable()
        {
            faceManager.facesChanged -= OnFaceChanged;
        }

        private void OnFaceChanged(ARFacesChangedEventArgs eventArgs)
        {
            if (eventArgs.updated.Count != 0)
            {
                var arFace = eventArgs.updated[0];
                if (arFace.trackingState == TrackingState.Tracking
                    && (ARSession.state > ARSessionState.Ready))
                {
                    // デバックのために顔のpositionを表示
                    Debug.Log($"face position: {arFace.transform.position}");
                }
            }
        }
    }
    ```

    ![スクリーンショット 2021-12-16 15.13.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/0eb2a346-2d64-3a7e-a2b2-320847035372.png)

1. ビルド
次にビルドをして正しく顔の情報が取得できているか確認してみようと思います。Unityのメニューの[File] -> [Build Settings...]を開いてください。最初にビルド対象に現在のシーンを追加するために、__[Add Open Scenes]を押してScenes In BuildにSampleSceneが追加されたかを確認__してください。次にPlatformの中にあるiOSを選択して[Switch Platform]を押してビルド対象をiOSに切り替えてください。次に[Player Settings]を開いて、その中の[Other Settings]の中の[Camera Usage Description]と、[Requires ARKit support]を以下の様に変更してください。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/f877a921-034f-514d-2077-4c8c8023c430.png" width="500">
また、同じく[Player Settings]にある[XR Plug-in Management]のARKitにチェックを入れて、その下の[ARKit]メニューの[Face Tracking]にもチェックを入れてください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/9faef5fa-1ccf-7430-c6f0-19765b9d4e47.png" width="600">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/3ea7e786-1d05-89f9-34fd-930606ad6113.png" width="600">
[Build]をクリックして、適当なiOSなどのフォルダを作成し、[Choose]を押します。そうすると、ビルドが終わるとXcodeが起動するので、実機を使って動作を確認します。その際にteamの設定をしていないとビルドできないので、各自で設定するようにしてください。アプリが起動すると、カメラのパーミッションを確認され、OKを押すとインカメラの映像が表示されます。Xcodeのログに以下のような出力がされ、顔を動かすとこの値が変化していくことが確認できれば大丈夫です。ARKitを使ってインカメラに写っている自分の顔の位置などの情報を取得することができる様になりました。

    ```C#
    face position: (-0.1, 0.2, 0.4)
    FaceTracking:OnFaceChanged(ARFacesChangedEventArgs)
    System.Action`1:Invoke(T)
    UnityEngine.XR.ARFoundation.ARFaceManager:OnTrackablesChanged(List`1, List`1, List`1)
    UnityEngine.XR.ARFoundation.ARTrackableManager`5:Update()
    ```
    
    ### ３Dアバターを取得した顔の情報を元に動かす
    最後に3DアバターをARKitから取得した情報を元に動かすということを行なっていきます。
    
    1. スクリプトの作成
    最初に先程のスクリプトにアバターを表示させる処理を追加していこうと思います。それが以下のスクリプトになります。(今更ですが、FaceTrackingというファイル名はあってなかったですね。)
    headOffsetを設定していたり、VRMBlendShapeProxyがいきなり登場していたり少し複雑な部分はあるのですが、実際にコードを変えてみてどういった動作をしているのかを確認してみるとより理解が深まると思います。また、__インスペクターからAvatar Prefabにモデルのプレハブをセットすることを忘れないでください。__<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/23b8fa4a-4989-4b40-76fe-4a4105f1c48d.png" width="500">

    ```C#
    using System.Collections.Generic;
    using Unity.Collections;
    using UnityEngine;
    using UnityEngine.XR.ARFoundation;
    using UnityEngine.XR.ARKit;
    using UnityEngine.XR.ARSubsystems;
    using VRM;
    
    public class FaceTracking : MonoBehaviour
    {
        [SerializeField] private ARFaceManager faceManager;
        [SerializeField] private GameObject avatarPrefab;
        ARKitFaceSubsystem faceSubsystem;
        GameObject avatar;
        Transform neck;
        Vector3 headOffset;
        VRMBlendShapeProxy blendShapeProxy;
    
        private void Start()
        {
            faceSubsystem = (ARKitFaceSubsystem)faceManager.subsystem;
            avatar = Instantiate(avatarPrefab);
    
            // 初期状態で後ろを向いてるため180度回転
            avatar.transform.Rotate(new Vector3(0f, 180f, 0));
    
            // 必要な首の関節のTransformを取得
            var animator = avatar.GetComponent<Animator>();
            neck = animator.GetBoneTransform(HumanBodyBones.Neck);
    
            // アバターの原点座標は足元のため、顔の高さを一致させるためにオフセットを取得
            var head = animator.GetBoneTransform(HumanBodyBones.Head);
            headOffset = new Vector3(0f, head.position.y, 0f);
    
            // VRMの表情を変化させるためのVRMBlendShapeProxyを取得
            blendShapeProxy = avatar.GetComponent<VRMBlendShapeProxy>();
        }
    
        private void OnEnable()
        {
            faceManager.facesChanged += OnFaceChanged;
        }
    
        private void OnDisable()
        {
            faceManager.facesChanged -= OnFaceChanged;
        }
    
        private void OnFaceChanged(ARFacesChangedEventArgs eventArgs)
        {
            if (eventArgs.updated.Count != 0)
            {
                var arFace = eventArgs.updated[0];
                if (arFace.trackingState == TrackingState.Tracking
                    && (ARSession.state > ARSessionState.Ready))
                {
                    UpdateAvatarPosition(arFace);
                    UpdateBlendShape(arFace);
                }
            }
        }
    
        private void UpdateAvatarPosition(ARFace arFace)
        {
            // アバターの位置と顔の向きを更新
            avatar.transform.position = arFace.transform.position - headOffset;
            neck.localRotation = Quaternion.Inverse(arFace.transform.rotation);
        }
    
        private void UpdateBlendShape(ARFace arFace)
        {
            // 瞬きと口の開き具合を取得して、アバターに反映
            var blendShapesVRM = new Dictionary<BlendShapeKey, float>();
            using var blendShapesARKit = faceSubsystem.GetBlendShapeCoefficients(arFace.trackableId, Allocator.Temp);
    
            foreach (var featureCoefficient in blendShapesARKit)
            {
                if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkLeft)
                {
                    blendShapesVRM.Add(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_L), featureCoefficient.coefficient);
                }
                if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkRight)
                {
                    blendShapesVRM.Add(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_R), featureCoefficient.coefficient);
                }
                if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.JawOpen)
                {
                    blendShapesVRM.Add(BlendShapeKey.CreateFromPreset(BlendShapePreset.O), featureCoefficient.coefficient);
                }
            }
            blendShapeProxy.SetValues(blendShapesVRM);
        }
    }
    ```

iPhone上でこんな感じで自分の顔の動きと重なるように3Dモデルが動いていたらほぼほぼ完成です。また、瞬きや口の開閉も正しく行える様になっているはずです。もっと完成度を上げようと思うと、さらに細かく顔に表情をつけたりポーズを変更したりなど色々なことができると思うので、もしよければ試してみてください。

:::note warn
アバターの向きや位置がずれる場合は、アプリの起動時のiPhoneの向きや位置が基準になるので、起動時にiPhoneを顔の正面に持ってくることで正しく表示される様になります
:::

![Screen Shot 2021-12-16 at 16.29.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/9f4d35d0-5d06-5a81-3292-95d883c025cb.png)

2. 背景を変更する
このままでもいいのですが、アバターだけを表示させたいので、最後に背景イメージを追加したいと思います。Hierarchyの+ボタンから[UI] -> [Canvas]を選択してシーンにCanvasを追加してください。そしてCanvasの[Render Mode]をScreen Space - Cameraにしてください。そして[Render Camera]にAR Cameraをセットして、[Plane Distance]を3に設定します。
その上でCanvasの子要素に、Imageを追加してください。[Rect Transform]はstretchにしてCanvasいっぱいに白い画像が表示される様にします。つまりカメラ画像をImageで隠すことによってやりたいことを実現しています。もっと、他にいい方法があるかもしれないのでもしよければ教えていただきたいです。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306038/b720def3-190f-650f-8f9c-9991ccbc894d.png" width="500">

最後にビルドして動かした時に、カメラ画像が表示されず、アバターだけが見える様になれば完成です！お疲れ様でした。

# 終わりに
途中からかなり雑になった部分はありますが、ARKitを使って得た顔の情報を元に、VRMのモデルを動かすということは達成できたと思います。何かエラーなどが発生したり、間違っている部分などがありましたらコメントいただけると、修正するかもしれません。僕自身、今やっていることを振り返りながらこの記事を作っていたのですが、終わってみれば必要なコードは非常に少なくこのようなアプリが作れるというのはUnityって改めてすごいなと感じました。