hz-unity-face-tracking について
ARKit の標準 52 種類のブレンドシェイプを Meta VR の顔トラッキングで動かす、Unity 向けの一連の手順をまとめたスキルです。ARKit リグのモデルを使う開発者向けで、公開 API の OVRCustomFace を使い、OVREyeGaze による視線制御も説明します。
主な機能
- 01ブレンドシェイプ名を走査してマッピングを作成
- 02OVRCustomFace による表情の駆動
- 03Meta XR トラッキングと必要な権限の設定
- 04OVREyeGaze による視線ブレンドシェイプや眼球ボーンの制御
- 05シーン設定、動作確認、トラブルシューティング
活用シーン
- 01Quest の顔トラッキングで ARKit リグの頭部モデルを動かす
- 02発話時に音声から口の表情を生成
- 03眼球ボーンのないモデルに視線ブレンドシェイプを接続
- 04ブレンドシェイプ未検出やトラッキング停止の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ meta-quest/ agentic-tools --skill hz-unity-face-trackingインストール後、Claude Code で/hz-unity-face-trackingを入力して読み込まれたか確認します。
同梱ファイルreferences/ARKitEyeGazeBlendshapeDriver.csreferences/ARKitOVRCustomFace.cs
詳細
ARKit リグ対応モデル向け Unity 顔トラッキング(Meta Movement SDK)
標準の 52 種類の ARKit ブレンドシェイプでリギングされた頭部/顔モデルを、Meta VR で装着者の顔の動きに合わせてアニメーションさせるための、エンドツーエンドの手順です。公開されている OVRCustomFace 拡張フックを使用するため、OVR_INTERNAL_CODE は不要で、サードパーティー向けに出荷できます。
モデルのブレンドシェイプ名は、ARKit の命名規則(キャメルケース、_L/_R のサフィックスなど。例:eyeBlink_L、jawOpen、mouthSmile_R)に従う必要があります。blendShape2.eyeBlink_L のような任意のプレフィックスは自動的に削除されます。
使用する場面
- ARKit 名のブレンドシェイプを持つ FBX/GLB/メッシュがあり、Meta VR の顔トラッキングで動かしたい場合。
- 「この頭部を自分の顔に合わせてアニメーションさせたい」「顔トラッキングでこれらのブレンドシェイプを動かしたい」「自分のモデルで Movement SDK A2E を使いたい」「ARKit シェイプを Meta VR につなぎたい」といった要望がある場合。
- 対象デバイス:Quest Pro、Quest 3、Quest 3S(顔カメラがない Quest 2 では何も起こりません)。
前提条件チェックリスト
- 顔トラッキング対応の Quest ヘッドセット(Pro/3/3S)。
Packages/manifest.json内のパッケージ:com.meta.xr.sdk.core(Meta XR Core —OVRFaceExpressions、OVRCustomFaceを提供)com.meta.xr.sdk.movement(Meta Movement SDK — A2E とリターゲティング用ヘルパー)
Assets/Oculus/OculusProjectConfig.asset(Project Settings → Meta XR で確認):faceTrackingSupport: 1(Supported)または2(Required)- リグに視線トラッキングがある場合は
eyeTrackingSupport: 1
- Android マニフェスト(
Assets/Plugins/Android/AndroidManifest.xml)の権限:<uses-feature android:name="oculus.software.face_tracking" android:required="false" /><uses-permission android:name="com.oculus.permission.FACE_TRACKING" /><uses-permission android:name="android.permission.RECORD_AUDIO" />(A2E に必要)- 視線トラッキングの場合:
oculus.software.eye_tracking+com.oculus.permission.EYE_TRACKING
- Audio(A2E)を含む
OVRManager.FaceTrackingDataSourcesが設定された、シーン内の OVRCameraRig — Audio を省略すると、口の動きは視覚情報のみに基づきます。
OculusProjectConfig を変更した後は、マニフェストを再生成してください(下記のマニフェストの再生成を参照)。
アプローチ(概要)
ARKitOVRCustomFaceスクリプト(references/ARKitOVRCustomFace.cs内)をプロジェクトに追加します。- OVRCameraRig(またはシーン内の任意の場所)に
OVRFaceExpressionsコンポーネントを追加します。 - モデルの
SkinnedMeshRendererがある GameObject にARKitOVRCustomFaceを追加します。コンポーネントを追加するとReset()が呼び出され、メッシュのブレンドシェイプ名をスキャンして、ブレンドシェイプから FaceExpression へのマッピングが自動的に設定されます。 - コンポーネントの
FaceExpressionsフィールドにOVRFaceExpressionsインスタンスを割り当てます。 - 視線トラッキングには、
ARKitEyeGazeBlendshapeDriverを追加します(下記の視線トラッキングを参照)。 - Meta XR Link で Play を押すか、APK をビルドしてサイドロードします。初回起動時に表示される顔トラッキング/視線トラッキング/マイクの権限プロンプトを承認します。
手順は以上です。詳細は以下をご覧ください。
視線追跡(必ず実施してください — 顔の表情だけでは機能しません)
視線の追跡に OVRFaceExpressions.EyesLook* を使用しないでください。 これらのフィールドは専用の視線トラッカーではなく、顔カメラの映像から算出されます。Quest Pro では、顔追跡自体が機能していても、値がゼロになったり不安定になったりすることがよくあります。適切な API は、視線トラッカーに直接アクセスする OVREyeGaze です。
eyeLook* ブレンドシェイプを使用する ARKit リグ(目のボーンなし)向けに、このスキルには ARKitEyeGazeBlendshapeDriver が含まれています。
- 2 つの
OVREyeGazeコンポーネント(左右それぞれ 1 つ、TrackingMode = HeadSpace)から視線の回転を読み取ります。 - 頭部の基準(例:
CenterEyeAnchor)に対する回転を計算します。 - ピッチを
eyeLookUp/Down_{L,R}に、ヨーをeyeLookIn/Out_{L,R}に分解します(ARKit の「_In = 鼻側」という規則に従います)。 LateUpdateでウェイトを書き込むため、UpdateでARKitOVRCustomFaceが書き込んだ値を上書きします。
目のボーンを使用するリグ(eyeLook* ブレンドシェイプなし)では、ブレンドシェイプへの分解は行わず、ApplyRotation = true にして OVREyeGaze を各目のボーンの子にするだけです。コンポーネントがボーンを直接回転させます。
視線の設定(ブレンドシェイプのリグ)
CenterEyeAnchor(または頭部の Transform)の子として、空の GameObject を 2 つ作成します。名前はLeftEyeGazeとRightEyeGazeにします。- それぞれに
OVREyeGazeを追加します。Eye = Left/Right、TrackingMode = HeadSpace、ApplyPosition = false、ApplyRotation = true、ConfidenceThreshold = 0.5に設定します。 - 頭部の SkinnedMeshRenderer GameObject に
ARKitEyeGazeBlendshapeDriverを追加します(ARKitOVRCustomFaceと同じ GameObject に追加します)。leftEye、rightEye、およびreferenceFrame(CenterEyeAnchor)を設定します。 maxAngleDeg(デフォルトは 30°)とsmoothing(デフォルトは 0.4)を好みに合わせて調整します。
手順
1. スクリプトをインストールする
references/ARKitOVRCustomFace.cs を Assets/Scripts/ARKitOVRCustomFace.cs にコピーします。このスクリプトは、公開され、サードパーティー製品に同梱できる ARKit ↔ OVR FaceExpression 対応表と、SkinnedMeshRenderer.sharedMesh.GetBlendShapeName(i) をスキャンし、最後の . より前のプレフィックスを削除して小文字に変換し、対応表と照合する MapBlendshapes() メソッドを定義しています。対応するものがないメッシュのブレンドシェイプには OVRFaceExpressions.FaceExpression.Max(センチネル値 — 実行時にスキップされます)が設定されます。
2. Movement SDK と権限を有効にする
プロジェクト設定:
Project Settings → Meta XR → Face Tracking Support = Supported
Project Settings → Meta XR → Eye Tracking Support = Supported (if needed)
続いて、マニフェストを再生成します。
マニフェストの再生成
Editor が起動している状態で、run_script を使って SDK のジェネレーターを呼び出します。型を直接解決できるため、リフレクションは不要です。
// AgentScripts/UpdateManifest.cs
public static class UpdateManifest
{
public static string Run()
{
OVRManifestPreprocessor.GenerateOrUpdateAndroidManifest(true); // silentMode: true
return "ok";
}
}
unity command run_script --file AgentScripts/UpdateManifest.cs --entry UpdateManifest.Run --format json
silentMode: true は必須です。これを指定しないと、ジェネレーターがモーダルダイアログを開き、リクエストがブロックされます。次に、上記のチェックリストにある顔追跡のエントリーが Assets/Plugins/Android/AndroidManifest.xml に実際に反映されていることを確認します。マニフェストのワークフロー全体と、管理対象のエントリーを手動編集しないというルールについては、hz-unity-meta-core-sdk を参照してください。
代替手段:Editor メニューの Meta → Tools → Update AndroidManifest.xml、または Meta Unity 拡張機能を使用する Unity MCP サーバー上であれば、meta_update_android_manifest ツールを使用します。
3. シーンを設定する
Scene Hierarchy
├── OVRCameraRig (OVRQuickActionsAPI.AddOVRInteractionRig(), or the
│ │ OVRCameraRig prefab; meta_add_camerarig under MCP)
│ └── (add) OVRFaceExpressions component
└── YourHeadModel
└── ...SkinnedMeshRenderer GO...
├── SkinnedMeshRenderer (existing)
└── (add) ARKitOVRCustomFace component
└── FaceExpressions = the OVRFaceExpressions ref
└── retargetingType = Custom (set automatically by base when overriding)
└── Mappings[] = auto-filled on Reset()
└── BlendShapeStrengthMultiplier = 100 (default; OVR weights are 0–1, mesh wants 0–100)
頭部に複数の SkinnedMeshRenderer(歯や舌のメッシュが分かれている場合など)がある場合は、それぞれに ARKitOVRCustomFace を追加します。
4. OVRManager — データソースとして Audio を有効にする(A2E)
OVRCameraRig の OVRManager コンポーネントで:
- Face Tracking Data Sources → Visual と Audio の両方にチェックを入れます(Audio = A2E。顔カメラで認識できない場合、たとえば発話中や遮蔽されている場合に、マイクから口の形状を生成します)。
5. コンポーネントがすでに存在する場合にマッピングを実行する
MapBlendshapes() はコンポーネントが追加されたとき(Reset())と、Mappings が空の場合に OnValidate() から自動的に実行されます。手動で再マッピングする必要がある場合(例:FBX を再インポートした後)は、コンポーネントのインスペクターのコンテキストメニューから Map Blendshapes を選択するか、スクリプトから実行します。
go.GetComponent<ARKitOVRCustomFace>().MapBlendshapes();
run_script を使って稼働中の Editor に対して実行する場合、プロジェクトスクリプトの型は直接参照できるため(Assets/ にあります)、そのまま呼び出します。
// AgentScripts/Remap.cs
using UnityEngine;
public static class Remap
{
public static string Run(string modelName)
{
var smr = GameObject.Find(modelName).GetComponentInChildren<SkinnedMeshRenderer>();
smr.gameObject.GetComponent<ARKitOVRCustomFace>().MapBlendshapes();
return $"Remapped {smr.sharedMesh.blendShapeCount} blendshapes on {smr.name}";
}
}
unity command run_script --file AgentScripts/Remap.cs --entry Remap.Run --args '["YourHeadModel"]' --format json
unity command save_scene --format json
Unity MCP サーバーでは、動的アセンブリから OVR/MSDK の型を参照できないため、リフレクションを使わない回避策として SendMessage を使用します。
GameObject.Find("YourHeadModel")
.GetComponentInChildren<SkinnedMeshRenderer>().gameObject
.SendMessage("MapBlendshapes", SendMessageOptions.RequireReceiver);
6. 確認する
-
Editor 内で、Meta XR Link / Quest Link を使用して Play モードに入り、顔を動かします。モデルもその動きを反映するはずです。この処理を CLI から実行するには、次のようにします。
unity command clear_console --format json unity command editor_play --format json # blocks until play mode is live # --- the wearer now makes faces; ask the user to confirm before continuing --- unity command get_console_logs --severity error --format json unity command editor_stop --format jsonこの4つを続けて実行しないでください。
editor_playの直後にeditor_stopを実行すると、誰かが顔を動かす時間がないため、確認になりません。Play モードに入り、ユーザーに顔を動かしてもらって見えた結果を報告してもらってから、ログを読み取って停止してください。 -
デバイス上で、APK をビルドしてサイドロードし(
metavr app install <apk>)、初回起動時に Face Tracking と Microphone の権限を許可します。顔を動かしながらmetavr adb logcat --follow --tag Unityをストリーミングし、マッピングエラーがないか確認してください。 -
編集時にマッピングを確認する:
ARKitOVRCustomFaceコンポーネントを調べます。Mappings.LengthはSkinnedMeshRenderer.sharedMesh.blendShapeCountと一致する必要があります。MapBlendshapes()の Console ログにはmapped X/N blendshapesと表示されます。X は 50(FBX にすべて含まれている場合は 52)になるはずです。インスペクターを開かずにシリアライズされた配列の長さを読み取るには、--fieldに SerializedProperty のパスを指定します。そのため、シリアライズされていない公開プロパティMappingsではなく、OVRCustomFaceの[SerializeField]のバッキングフィールド_mappingsを指定します。unity command get_serialized_fields --target <head GO> --component ARKitOVRCustomFace --field _mappings --format json
トラブルシューティング
| 症状 | 原因/対処方法 |
|---|---|
Console に mapped 0/N と表示される | メッシュのブレンドシェイプ名が ARKit の命名規則に従っていません。mesh.GetBlendShapeName(i) で確認してください。名前は eyeBlink_L、jawOpen などと一致する必要があります。大文字と小文字は区別されず、最後の . より前のプレフィックスは削除されます。 |
| 顔が動かない | OVRFaceExpressions が割り当てられていないか、シーンに顔トラッキングを有効にした OVRCameraRig/OVRManager がありません。実行時に OVRFaceExpressions.FaceTrackingEnabled と ValidExpressions を確認してください。 |
| 話しても口が動かない | A2E が無効になっています。OVRManager → Face Tracking Data Sources の Audio を有効にし、デバイスで RECORD_AUDIO 権限が許可されていることを確認してください。 |
| 目がまばたきしない | メッシュのまぶたのシェイプ名が eyeBlink_L/_R になっていません。名前を変更するか、ARKitTable にカスタム行を追加してください。 |
| 目が動かない(視線を動かせない) | OVREyeGaze ではなく OVRFaceExpressions.EyesLook* に依存しています。ARKitEyeGazeBlendshapeDriver に切り替えてください。 |
| 視線の向きが間違っている | ARKit と OVR の意図的な対応の入れ替わりに注意してください。ARKit の eyeLookIn_L(鼻の方向、つまり右を向く目)は OVR の EyesLookRightL に対応します。テーブルにはすでに反映されているため、「修正」しないでください。 |
| ウェイトが半分の強さに見える、または制限される | Unity のブレンドシェイプは 0~100、OVR は 0~1 のため、BlendShapeStrengthMultiplier の既定値は 100 です。意図的な場合を除き、この値を下げないでください。 |
コンパイルエラー cannot change access modifiers when overriding が発生する | 別のアセンブリにある基底メソッドは protected internal です。オーバーライドには protected(protected internal ではなく)を指定する必要があります。提供されているスクリプトではすでに正しく設定されています。 |
Start 時に _mappings out of sync with shared mesh アサーションが発生する | マッピング生成後にメッシュが変更されています。コンポーネントのコンテキストメニューから MapBlendshapes() を再実行してください。 |
ARKit ↔ OVR FaceExpression のマッピング(参考)
52 個の ARKit シェイプは、FACS ベースの OVR 列挙型と 1 対 1 で対応していません。ARKitTable に組み込まれている主な選択は次のとおりです。
- 中央の名前が付いた、L+R の OVR ペアを持つ ARKit シェイプ(
browInnerUp、cheekPuff、mouthFunnel、mouthPucker、mouthRollLower、mouthRollUpper)では、OVR 表情の L 側のみを選択します。ユーザーがこれらの表情を使ったときにモデルの口や眉が明らかに非対称になり、真に対称的に動かしたい場合は、サブクラスを作成し、カスタムのMapBlendshapesで L+R を加算してください(SlothARKitFaceDriverのようにブレンドシェイプごとに合算します)。ほとんどの顔モデルでは、顔がおおむね対称で、非対称性が視覚的に気にならない範囲に収まるため、L 側のみを使う方法で問題ありません。 eyeLookIn/Outは、OVR の左右の意味に対して、意図的に左右が入れ替わっています(トラブルシューティングを参照)。mouthClose→LipsToward(最も近い FACS の類似表現です)。tongueOut→TongueOut(HorizonOS ≥ 65 と舌トラッキングが必要です。それ以外の場合は 0 のままです)。
このスキル内のファイル
SKILL.md— このファイルreferences/ARKitOVRCustomFace.cs— そのまま使えるOVRCustomFaceサブクラスreferences/ARKitEyeGazeBlendshapeDriver.cs— OVREyeGaze →eyeLook*ブレンドシェイプドライバー(LateUpdate)
この方法を選ぶ理由(他の方法との比較)
MonoBehaviourカスタムドライバーでOVRFaceExpressions[expr]を読み取り、ブレンドシェイプのウェイトを直接書き込みます。動作はしますが、MSDK の補正、目の制約、今後のリターゲティングのアップグレードと連携しません。OVRCustomFaceを拡張できない場合にのみ使用してください。OVRCustomFace+RetargetingType.ARKitBlendshapes:シンプルですが、公開 Oculus Integration パッケージでは#if OVR_INTERNAL_CODEの背後にあるため、サードパーティー向けに出荷できません。OVRCustomFace+RetargetingType.Custom+GetCustomBlendShapeNameAndExpressionPairsのオーバーライド(このスキル):完全に公開された API で、出荷可能です。また、すべての基底クラスの動作(データの有効性チェック、ウェイトのスケーリング、メッシュのアサーション)を利用できます。