关于 hz-unity-face-tracking
此技能提供在 Unity 中将标准 52 个 ARKit blendshape 连接到 Meta VR 面部追踪的完整流程,面向使用 ARKit 绑定模型的开发者。它使用公开的 OVRCustomFace 扩展点,并另外说明如何通过 OVREyeGaze 驱动眼神。
主要功能
- 01扫描 blendshape 名称并生成映射
- 02通过 OVRCustomFace 驱动面部表情
- 03配置 Meta XR 面部追踪与所需权限
- 04用 OVREyeGaze 驱动眼神 blendshape 或眼骨
- 05提供场景设置、验证和故障排查步骤
适用场景
- 01让 ARKit 绑定的头部模型响应 Quest 面部追踪
- 02在说话时通过音频驱动口部表情
- 03为没有眼骨的模型连接眼神 blendshape
- 04排查 blendshape 未匹配或追踪无响应
安装
复制下面的命令,在终端中运行。
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 Rig 模型的 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
- 场景中包含 OVRCameraRig,且其
OVRManager.FaceTrackingDataSources包含Audio(A2E)——如果跳过 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* blendshape(没有眼骨骼)的 ARKit 骨骼,本技能提供了 ARKitEyeGazeBlendshapeDriver:
- 从两个
OVREyeGaze组件读取视线旋转(每只眼睛一个,TrackingMode = HeadSpace)。 - 计算相对于头部参考对象(例如
CenterEyeAnchor)的旋转。 - 将俯仰角分解为 →
eyeLookUp/Down_{L,R},将偏航角分解为 →eyeLookIn/Out_{L,R}(遵循 ARKit 的“_In = 朝向鼻子”约定)。 - 在
LateUpdate中写入权重,因此会覆盖ARKitOVRCustomFace在Update中写入的值。
对于带有眼骨骼(没有 eyeLook* blendshape)的骨骼,跳过 blendshape 分解,直接将 OVREyeGaze 分别设为每个眼骨骼的父对象,并将 ApplyRotation = true——该组件会直接旋转骨骼。
眼动设置(blendshape 骨骼)
- 在
CenterEyeAnchor(或头部变换)下创建两个空 GameObject:LeftEyeGaze、RightEyeGaze。 - 分别添加
OVREyeGaze。设置Eye = Left/Right、TrackingMode = HeadSpace、ApplyPosition = false、ApplyRotation = true、ConfidenceThreshold = 0.5。 - 将
ARKitEyeGazeBlendshapeDriver添加到头部的 SkinnedMeshRenderer GameObject(与ARKitOVRCustomFace一起)。连接leftEye、rightEye和referenceFrame(即CenterEyeAnchor)。 - 根据需要调整
maxAngleDeg(默认 30°)和smoothing(默认 0.4)。
分步说明
1. 安装脚本
将 references/ARKitOVRCustomFace.cs 复制到 Assets/Scripts/ARKitOVRCustomFace.cs。它定义了公开且可随第三方产品发布的 ARKit ↔ OVR FaceExpression 对照表,以及 MapBlendshapes() 方法。该方法会扫描 SkinnedMeshRenderer.sharedMesh.GetBlendShapeName(i),去掉最后一个 . 之前的所有前缀,将名称转为小写,并与对照表匹配。未匹配的网格 blendshape 会被设为 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. 如果组件已存在,则触发映射
添加组件时(Reset())以及 Mappings 为空时的 OnValidate() 中,都会自动运行 MapBlendshapes()。如果需要手动重新映射(例如重新导入 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不要连续执行这四项操作。 在
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)。无需打开 Inspector 即可读取序列化数组长度——--field接受的是 SerializedProperty 路径,因此需要使用OVRCustomFace上的[SerializeField]后备字段_mappings,而不是未序列化的公共属性Mappings:unity command get_serialized_fields --target <head GO> --component ARKitOVRCustomFace --field _mappings --format json
故障排除
| 症状 | 原因 / 解决方法 |
|---|---|
Console 中显示 mapped 0/N | 网格的 blendshape 名称不符合 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 添加自定义行。 |
| 眼睛不动(无法环顾四周) | 你依赖的是 OVRFaceExpressions.EyesLook*,而非 OVREyeGaze——请切换到 ARKitEyeGazeBlendshapeDriver。 |
| 眼球注视方向错误 | 请注意 ARKit ↔ OVR 之间的有意交换:ARKit 的 eyeLookIn_L(眼睛朝鼻侧看,即向右)映射到 OVR 的 EyesLookRightL。表中已经如此设置——不要“修正”它。 |
| 权重看起来只有一半强度或被钳制 | BlendShapeStrengthMultiplier 默认值为 100,因为 Unity blendshape 的范围是 0–100,而 OVR 的范围是 0–1。除非有意为之,否则不要调低此值。 |
编译错误 cannot change access modifiers when overriding | 基类方法在另一个程序集中为 protected internal。重写时必须使用 protected(而非 protected internal)——提供的脚本中已正确设置。 |
启动时断言 _mappings out of sync with shared mesh | 生成映射后网格发生了变化。通过组件上下文菜单重新运行 MapBlendshapes()。 |
ARKit ↔ OVR FaceExpression 映射(参考)
52 个 ARKit 形状与基于 FACS 的 OVR 枚举并非一一对应。ARKitTable 中包含以下值得注意的选择:
- 名称居中的 ARKit 形状对应一对左右 OVR 表情(
browInnerUp、cheekPuff、mouthFunnel、mouthPucker、mouthRollLower、mouthRollUpper),此时仅选择 左侧 OVR 表情。如果用户使用这些表情时,模型的嘴部或眉毛明显不对称,而你希望实现真正对称的驱动,可创建子类,并在自定义MapBlendshapes中将左右两侧相加(采用类似SlothARKitFaceDriver的逐个混合形状求和方式)。对于大多数头部模型,选择左侧即可,因为面部大致对称,且不对称程度低于视觉阈值。 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保护——无法作为 3P 产品发布。OVRCustomFace+RetargetingType.Custom+GetCustomBlendShapeNameAndExpressionPairs重写(本技能):完全使用公开 API,可发布,并且能获得基类的所有行为(数据有效性门控、权重缩放、网格断言)。