UnityのARKitでFaceTrackingしてLive2Dキャラクターを動かす方法

# :one: はじめに
Uaalを使用して、Swiftのプロジェクトの一部にUnityを導入しています。

### 今回はUnityのARKitでFaceTrackingしてLive2Dキャラクターを動かす方法を解説して行きます！

| 完成図|
| ---- | 
| ![RPReplay_Final1710125779 2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/0c8b3a7b-5826-2662-5efc-0a83edd28f3a.gif)| 



## 環境
- Unity 2021.3.3 (2020.3以上のverを使用しないとできないっぽいです！)
- jetBrains Rider2023.3.3
- 2DSprite 1,0,0
- ARFoundation 4.2.10
- ARCoreXRPlagin 4.2.10
- ARKitXRPlagin 4.2.10

iOSX以上の端末であればARKitを使って顔の情報を取得することが可能です

:::note warn
Live2DのPackageとARのPackageをすべて入れたところbuildが通らなくなりました。そのためLive2D側は2DSpriteのみ入れています。
:::


# :two: ARKitでFaceTrackingを行ってみる



## :point_up:必要なPackageをImportする


必要なpackageは **ARFoundation,　ARCoreXRPlagin,　ARKitXRPlagin**　です!

window>PackageManager

![スクリーンショット 2024-03-08 16.58.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/2775dbc1-8ce9-2633-5003-43fe19440f29.png)

UnityRegistry>検索から上記3つを検索し、importします！


:::note info

PackageManagerから入れた各種packageのverはUnityのverに依存する認識です
:::


## :point_up:プラグインの設定を行う

1、Edit>Project Settings> XRPlaginManagement

2、iOS選択して　ARKitにチェックマーク

![スクリーンショット 2024-03-08 17.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/ddbbbe2b-9dc3-e0c7-dafe-88cf371ed2a1.png)

## :point_up:プレイヤーのセッティングを行う

1、File>BuidSettings>PlayerSettings>Player>configuration> CamerUsageDescriptionに
   ”ARを使用するためカメラを使用します”などと記載します！


2、Archtecture > “ARM64”に設定




## :point_up:必要なオブジェクトをHierarchy内に設置

上記で、ARKitでFaceTrackingする環境は整いました！

次にFaceTrackingするために必要なオブジェクトを追加していきます！



## :point_up:ARSessionOriginと ARSessionを追加する

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3630383332382f65666663633336662d303234662d346635342d346137322d3530326565656437323063312e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/4256728b-2607-b4d1-a8dd-db506063db22.png)


ARSessionOriginnを追加すると自動でARCameraが子オブジェクト配置されます



今回私は、ARCameraで取得した情報をLive2Dのアバターに適応したかったので、ARCameraで自身の顔情報を取得してMainCameraでアバターを表示しています。Hierarchyの配置は下記のようにしました！
![スクリーンショット 2024-03-11 9.05.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/b5dc28b1-8f83-95d7-b674-38243e7eb7fd.png)

:::note info
初学者なためこれより良い方法があれば教えていただきたいです！
:::




## :point_up:AR Session OriginオブジェクトにAR Face Managerを追加

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3330363033382f37666463386639342d633065372d326166352d366235372d3639306531623733356330652e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/89fb6254-ba7f-4260-7366-1308b507234a.png)

ARFaceManegerをセットすることで顔認識を行うことができます！


## :point_up:ARCameraのInspector内のFachingDirectionをUserに変更
![スクリーンショット 2024-03-08 17.33.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/83e91370-ce1b-ce39-ad87-8dff88c8a0b1.png)

Userにすることで、インカメを使用することを設定できます！

## :point_up:FaceTrackingの情報を取得するスクリプトを空のgameObjectに追加

```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))
            {
                Debug.Log(arFace.transform.position);
            }
        }
    }
}

```

Scene内に空のGameObjectを配置してスクリプトを追加します！
[SerializeField]で宣言しているfaceManagerにARSessionOriginを紐づけます！
![スクリーンショット 2024-03-11 9.17.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/6461c683-1e71-704c-f56b-26cd94ac289a.png)



## :point_up:実機でBuildしてみる

私は、Uaalを用いてSwiftプロジェクトの一画面に表示しました!
![スクリーンショット 2024-03-08 17.50.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/455ead5e-eb00-dcd7-436b-932bfa3dac15.png)


顔を動かすと、上記の数字が都度変化することがわかると思います！

ここまで無事実装できたら、あとはLive2Dのキャラクターに情報を更新するだけです！:sunny:



# :three: Live2Dキャラクターを配置して動かしてみる

今回はLive2DとARKitでのFaceTrackingを主に解説するのでLive2Dのキャラクターの配置などはサラッと解説します！

参考URL: https://www.live2d.com/learn/sample/
上記URLからキャラクターを取得できます！

参考URL: https://www.live2d.com/sdk/download/unity/
上記URLから、Live2Dのアバター操作に必要なpackaegsをインストールできました！


:::note warn
注意
自分はすべてLive2Dのpackagesをすべてインストールするのではなく2DSpriteのみインストールしました！
:::

## :point_up:Canvasの子オブジェクトにインストールしたアバターのプレハブ配置

※ここで使用するCanvasのRenderCameraはMainCameraにしています！

![スクリーンショット 2024-03-11 10.08.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/5a823fdd-8b1a-c5b7-ef2f-701492da8f2a.png)


## :point_up:スクリプトを修正して、アバターのパラメータを修正する

はじめに全体のコードを載せておきます！


```C#
using UnityEngine;
using Unity.Collections;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.ARKit;
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework;
using Unity.VisualScripting;

public class FaceTracking : MonoBehaviour
{
    [SerializeField] private ARFaceManager faceManager;
    [SerializeField] private GameObject avatarPrefab;
    private CubismModel live2DModel;
    ARKitFaceSubsystem faceSubsystem;
    
    

    private CubismParameter faceAngleX;
    private CubismParameter faceAngleY;
    private CubismParameter faceAngleZ;
    private CubismParameter bodyAngleX;
    private CubismParameter bodyAngleY;
    private CubismParameter leftEye;
    private CubismParameter rightEye;
    private CubismParameter mouthForm;
    private CubismParameter mouthOpen;
    private CubismParameter eyeBallX;
    private CubismParameter eyeBallY;
    
    private float updateFaceAngleX;
    private float updateFaceAngleY;
    private float updateFaceAngleZ;
    private float updateLeftEye;
    private float updateRightEye;
    private float updateMouthForm;
    private float updateMouthOpen;
    
    // LifeCycle

    private void Start()
    {
        Application.targetFrameRate = 60;
        live2DModel = avatarPrefab.GetComponent<CubismModel>();
        
        // 表示したアバターとARKitを同期させる
        SetCubismParameter(live2DModel);
    }

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

    private void OnDisable()
    {
        faceManager.facesChanged -= OnFaceChanged;
    }
    
    private void LateUpdate()
    {
        // 表情
        leftEye.Value = updateLeftEye;
        rightEye.Value = updateRightEye;
        mouthForm.Value = updateMouthForm;
        mouthOpen.Value = updateMouthOpen;
        
        // // 顔の向き
        faceAngleX.Value = updateFaceAngleX;
        faceAngleY.Value = updateFaceAngleY;
        faceAngleZ.Value = updateFaceAngleZ;
    }
    
    private void OnFaceChanged(ARFacesChangedEventArgs eventArgs)
    {
        if (eventArgs.updated.Count != 0)
        {
            var arFace = eventArgs.updated[0];
            if (arFace.trackingState == TrackingState.Tracking
                && (ARSession.state > ARSessionState.Ready))
            {
                UpdateFaceTransform(arFace);
                UpdateBlendShape(arFace);
            }
        }
    }
    
    // Private 
    
    // 変数にアバターのモーションを定義する
    private void SetCubismParameter(CubismModel model)
    {
        faceAngleX = model.Parameters[0];
        faceAngleY = model.Parameters[1];
        faceAngleZ = model.Parameters[2];
        bodyAngleX = model.Parameters[22];
        bodyAngleY = model.Parameters[23];
        leftEye = model.Parameters[3];
        rightEye = model.Parameters[5];
        mouthForm = model.Parameters[17];
        mouthOpen = model.Parameters[18];
    }

    //顔の向きを更新する
    private void UpdateFaceTransform(ARFace arFace)
    {
        // 顔の位置データを取得
        Quaternion faceRotation = arFace.transform.rotation;
        
        float x = NormalizeAngle(faceRotation.eulerAngles.x)* 2f;
        float y = NormalizeAngle(faceRotation.eulerAngles.y);
        float z = NormalizeAngle(faceRotation.eulerAngles.z)* 2f;
    
        // 新しい顔の情報を変数にいれる
        updateFaceAngleX = y;
        updateFaceAngleY = x;
        updateFaceAngleZ = z;
    }
    
    // 表情を更新する
    private void UpdateBlendShape(ARFace arFace)
    {
        faceSubsystem = (ARKitFaceSubsystem)faceManager.subsystem;
        using var blendShapesARKit = faceSubsystem.GetBlendShapeCoefficients(arFace.trackableId, Allocator.Temp);
        foreach (var featureCoefficient in blendShapesARKit)
        {
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkLeft)
            {
                updateLeftEye = 1 - featureCoefficient.coefficient;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkRight)
            {
                updateRightEye = 1 - featureCoefficient.coefficient;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.MouthFunnel)
            {
                updateMouthForm = 1 - featureCoefficient.coefficient * 2;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.JawOpen)
            {
                updateMouthOpen = (float)(featureCoefficient.coefficient * 1.8);
            }
        }
    }
    
    // 顔の角度を正規化する
    private float NormalizeAngle(float angle)
    {
        if (angle > 180)
        {
            return angle - 360;
        }
        return angle;
    }
}

```

:::note warn
注意
[SerializeField]で宣言しているavatarPrefabには必ずScene内に配置しているavatarのPrefabをセットしてください！
:::

私はここで半日ハマりました。


詳細を説明していきます！:raised_hand:

# :four: コード内詳細

## :point_up:アバターのパラメーターを指定する



```C#
// 変数にアバターのモーションを定義する
    private void SetCubismParameter(CubismModel model)
    {
        faceAngleX = model.Parameters[0];
        faceAngleY = model.Parameters[1];
        faceAngleZ = model.Parameters[2];
        bodyAngleX = model.Parameters[22];
        bodyAngleY = model.Parameters[23];
        leftEye = model.Parameters[3];
        rightEye = model.Parameters[5];
        mouthForm = model.Parameters[17];
        mouthOpen = model.Parameters[18];
    }

``` 

Start関数で呼ばれているSetCubismParameterでアバタープレハブ内にあるパラメータを変数に入れてます！

配列のIndex指定は下記画像、上から0,1,2,3で指定できます！
例) faceAngleXにはPARAM_ANGLE_Xを入れたかったので,model.Parameters[0]のような感じです！
![スクリーンショット 2024-03-11 10.25.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1624020/b5457e7f-8c7a-9ab1-9969-72679b892056.png)


## :point_up:目、口のトラッキング

```C#

 // 表情を更新する
    private void UpdateBlendShape(ARFace arFace)
    {
        faceSubsystem = (ARKitFaceSubsystem)faceManager.subsystem;
        using var blendShapesARKit = faceSubsystem.GetBlendShapeCoefficients(arFace.trackableId, Allocator.Temp);
        foreach (var featureCoefficient in blendShapesARKit)
        {
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkLeft)
            {
                updateLeftEye = 1 - featureCoefficient.coefficient;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.EyeBlinkRight)
            {
                updateRightEye = 1 - featureCoefficient.coefficient;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.MouthFunnel)
            {
                updateMouthForm = 1 - featureCoefficient.coefficient * 2;
            }
            if (featureCoefficient.blendShapeLocation == ARKitBlendShapeLocation.JawOpen)
            {
                updateMouthOpen = (float)(featureCoefficient.coefficient * 1.8);
            }
        }
    }

```
ARFaceオブジェクトから顔の識別子（trackableId）を使用して、ARKitFaceSubsystemからブレンドシェイプの係数を取得します!

- EyeBlinkLeftとEyeBlinkRightの場合は、目の瞬きを表すパラメータを更新します。
- MouthFunnelの場合は、口の開閉を表すパラメータを更新します。係数に2を掛けて値の範囲を調整しています。
- JawOpenの場合は、口の開閉を表すパラメータを更新します。係数に1.8を掛けて値の範囲を調整しています。

更新された情報を(右目、左目,口)の動きを更新するために変数に入れます！
:bulb:顔の向きこの関数では扱っておりません！


## :point_up:顔の向きのトラッキング

```C#
//顔の向きを更新する
    private void UpdateFaceTransform(ARFace arFace)
    {
        // 顔の位置データを取得
        Quaternion faceRotation = arFace.transform.rotation;
        
        float x = NormalizeAngle(faceRotation.eulerAngles.x)* 2f;
        float y = NormalizeAngle(faceRotation.eulerAngles.y);
        float z = NormalizeAngle(faceRotation.eulerAngles.z)* 2f;
    
        // 新しい顔の情報を変数にいれる
        updateFaceAngleX = y;
        updateFaceAngleY = x;
        updateFaceAngleZ = z;
    }

     // 顔の角度を正規化する
    private float NormalizeAngle(float angle)
    {
        if (angle > 180)
        {
            return angle - 360;
        }
        return angle;
    }
```

ARFaceから顔の姿勢データを取得し、その回転を表すQuaternionを取得します。

取得したQuaternionから、x軸（縦方向）の回転角度を取得し、NormalizeAngle関数を使用して角度を正規化します。正規化された角度は0から360度の範囲になります。

値を2倍していますが、これはLive2Dのパラメータが通常-1から1の範囲で表されるため、ARKitの角度を適切にマッピングするためです。

正規化した値を更新するための変数に入れています！



## :point_up:都度FaceTrackingデータを取得して更新する

```C#
 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))
            {
                UpdateFaceTransform(arFace);
                UpdateBlendShape(arFace);
            }
        }
    }
```

## :point_up:Live2Dアバターのパラメータを更新する

```C#
 private void LateUpdate()
    {
        // 表情
        leftEye.Value = updateLeftEye;
        rightEye.Value = updateRightEye;
        mouthForm.Value = updateMouthForm;
        mouthOpen.Value = updateMouthOpen;
        
        // // 顔の向き
        faceAngleX.Value = updateFaceAngleX;
        faceAngleY.Value = updateFaceAngleY;
        faceAngleZ.Value = updateFaceAngleZ;
    }
    
```

:::note warn
警告
:bulb: Live2Dのパラメータ更新はLateUpdate()内で行う
:::



iPhoneX以上の端末でビルドし、自身の顔に合わせて動けば完成です！:v:



# :five: まとめ

私自身Switfでの開発経験はありましたが,Unityでの開発は初めてでした
コードの書き方や、オブジェクトの配置など不適切なところがあれば教えていただきたいです！

また、今回は必要最低限のパラメータのみ更新しているので、より多くのパラメータを更新することによりリッチなモーションを作成することも可能だと思います！

顔の詳細な角度や、振り幅なども改善の余地があると思います。

ARKitでのFaceTrackingを用いてLive2Dアバターを動かしたいと思っている方に少しでも参考になれば幸いです:v:



# 最後に
弊社では、経験の有無を問わず採用を行っています。
興味のある方は是非カジュアル面談しましょう！
https://jambo-support.com/recruit_engineer/