> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/lipsync-and-blendshape/metahuman-limit-drivers-required-for-mha.md).

# MetaHuman 限制驱动程序——\`mha\` 必需

Unreal 的 MetaHuman 骨骼对流式数据所依据的内置通道耦合施加限制。在原始 glTF 角色上，这些耦合不存在——最明显的是， `jawOpen` 还必须驱动四个 `mouthLipsTogether*` 个通道，使嘴唇在下颌运动时保持接触。 **如果没有这个，每次张嘴都会使嘴唇完全分开并露出牙齿** ——嘴巴只会乱动而不是说话，再怎么调也无济于事。

在写入 morph 之前，将它们应用到每一帧：

```ts
import { applyMhaLimits } from '@convai/web-sdk/lipsync-helpers';

applyMhaLimits(frame); // 原地：每个耦合的 target = max(target, source)
```

`MHA_LIMIT_DRIVERS` 如果你需要原始表，它会导出解析后的耦合表（例如 `jawOpen → jawOpenExtreme`, `jawOpen → mouthLipsTogetherUL/UR/DL/DR`, `eyeBlink → eyeLidPress`，以及同类项）。

***

### 让口型同步感觉自然且逼真

原始流播放听起来很机械。以下内容按系统分类，是区分演示和可信角色的关键。本指南是反复打磨一个 MetaHuman 风格（`mha`）角色，对照录制的近景 QA 反复迭代，直到开元音、爆破音、牙齿、眉毛、头部和眼睛都像真人视频一样自然——这里的每一个数值都是在这个过程中保留下来的，以及它所修复的失效问题。流形塑模式（§1）以一键处理器形式提供；程序化系统（§2–3）是实现指南，参考代码在 `examples/react-three-fiber/src/hooks`.

#### 1. 流形塑——嘴部（`mha`)

一次调用即可按正确顺序封装逐帧管线：

```ts
import { createLipsyncProcessor } from '@convai/web-sdk/lipsync-helpers';

const lipsync = createLipsyncProcessor('mha', {
  skipEyeChannels: true,           // 你会在 §2 中运行眨眼/注视
  gainOverrides: { jawOpen: 0.72 } // 按角色微调，见下表
});
const bindings = lipsync.bindAll(gltf.scene); // 修复名称，映射 morph 槽位

// 每次渲染 tick：
lipsync.tick(frameOrNull, delta, bindings);
```

内部流程： **对称化 → 限制耦合 → 增益 → 包络 → morphs。**

**1.1 骨骼限制耦合（必需）**

见上文——如果没有 `jawOpen → mouthLipsTogether*` 每次张嘴都会露出牙齿。处理器默认会应用它们。

**1.2 各通道增益——制作表**

该流是针对参考脸调好的；在真实角色上，原始值会把下颌张得过开、把嘴巴拖向一侧，并让嘴唇从牙齿上后缩。这张表是在“含糊不清/木偶嘴”（开口太小）和“整口露牙”（开口太大）之间找到的平衡点，并在镜头前沿着两个方向反复调出来：

| 通道                                  | 增益       | 原因                                   |
| ----------------------------------- | -------- | ------------------------------------ |
| `jawOpen`                           | **0.72** | 对话式开口。0.6 在开元音上会显得含糊；原始值（1.0）会露出牙齿。  |
| `jawOpenExtreme`                    | **0.42** | 同上，适用于限制驱动器提供的极限形状。                  |
| `jawLeft/Right`, `mouthLeft/Right`  | **0**    | 一旦你做了对称化（§1.3），侧向位移只会被看成歪嘴。          |
| `mouthUpperLipRaiseL/R`             | **0.6**  | 抬起上唇会露出上牙；只保留一丝可见即可。                 |
| `mouthLowerLipDepressL/R`           | **0.45** | 现实中，下唇几乎从不低于下切牙。                     |
| `mouthLipsTogetherUL/UR/DL/DR`      | **0.8**  | 在正常说话中，嘴唇会贴着牙齿；开元音仍会露出一点上牙。          |
| `mouthFunnel*`, `mouthLipsPurse*`   | **1.15** | 流对 /u/ /w/ 的圆唇驱动不足。                  |
| `mouthCornerPull*`, `mouthStretch*` | **1.0**  | 不要增强：在 /i/ /e/（EE）上放大的嘴角外展会显得像夸张的鬼脸。 |

**1.3 嘴部对称**

流对 L/R 成对通道的驱动并不均匀（测得最高可达 1.5×），这会被读成歪嘴——每一帧都要对所有带侧向的嘴部/下颌对进行平均。

**1.4 迸发与收敛包络**

第一帧不能把嘴突然弹开，最后一帧也不能卡在半个 viseme 上：在说话开始时，增益包络会在约 0.25 秒内渐入（起音 τ ≈ 0.09 秒），并在最后一帧之后约 0.5 秒内渐出（释放 τ ≈ 0.18 秒）。

**1.5 下颌硬性上限**

不受增益影响，钳制 *应用后的* `jawOpen` 在 **0.55**。增益会缩放一切；上限只会抑制那些在镜头前显得夸张的罕见峰值偏移，同时不影响正常发音。

**1.6 双唇闭合下限——为什么 /p/ /b/ /m/ 永远不会自行闭合**

这是结构性问题，任何增益都无法修复： `mouthLipsTogether*` 这些值来自 `jawOpen` 通过限制耦合——而该耦合在 **在爆破音闭口时恰好接近零**。所以在“pumpkin”这个词里，无论你把 lips-together 增益拉多高，嘴唇都会明显分开。修复方法：在说话时，当下颌闭合时直接驱动接触：

```ts
// 双唇闭合：当下颌（几乎）闭合时为 1，在 jawRaw ≈ 0.18 时淡出
const bilabial = 1 - clamp((jawRaw - 0.02) / 0.16, 0, 1);
if (bilabial > 0.45) {
  const seal = ((bilabial - 0.45) / 0.55) * 0.5 * envelope;
  lipsTogetherValue = Math.max(lipsTogetherValue, seal);
}
```

这个 **0.5 的上限很重要**：在 0.9 时，爆破音会被读成嘴唇碰撞（硬钳制）；在 0.65 时仍然压得过紧；0.5 才会被读成嘴唇 *接触*。闭合时测得的封口值约 0.5–0.6 才是目标。

**1.7 瞬态保留**

真实语音在硬辅音上有锐利的起音；过强的时间平滑会把它们糊成一团。使用轻度平滑系数（**lerp ≈ 0.92** 在 60 fps 下每帧一次，即几乎不平滑）——包络（§1.4）已经负责保护起止，因此逐帧平滑不必太强。

**1.8 音频同步——让嘴部视觉上领先**

人类读唇会略微 *提前* 于声音。如果录制内容显示嘴部落后于声音，则将帧播放提前约**100 毫秒** 于音频位置之前（60 fps 下为 6 帧）。使用 dt 累加器（每帧 1/60 秒）来消费队列——切勿将 `getFrameAtTime()` 与 `consumeFrames()`混用，否则会双重推进并让嘴部跟不上。

**1.9 牙齿与口腔着色——“牙齿过多”问题的另一半**

只有当口腔内部像脸部一样被照亮时，开口大小和牙齿暴露才会互相冲突。真实的嘴巴处在阴影中。在这个栈里，最终让真实开口成为可能的修复是 **材质**，而不是动画：

* 把牙釉质色调调暗（约 0.4 的暖灰，从 0.55 起点开始）；
* 降低牙齿材质的环境/IBL 响应（`envMapIntensity` ≈ 0.1）以及高光（≈ 0.28）；
* 把上下牙列往嘴唇后方缩进几毫米，如果可以，把牙龈侧和嘴唇侧向阴影烘焙进牙齿着色中。

结果：开元音会呈现 *带阴影的一瞥* 上排牙齿——像真实影像一样——而不是一条亮白色横带；同时下牙看起来是隐藏的，又不会影响发音。

#### 2. 程序化面部生命——眼睛

流中的眼部通道应由 **程序化系统接管** （设置 `skipEyeChannels: true`）；不同计时器上的两个写入者会相互竞争，导致闪烁。

**2.1 眨眼**

人类每 2–6 秒眨一次眼。最简方案（约 10 行）：维护一个倒计时；触发时驱动 `eyeBlinkL/R` 通过 0.2 秒的正弦（闭合 → 张开），然后重新设定为 `2 + random()*4` 秒。再加上静息时眼睑轻微下垂（约 0.2）以及偶尔的半眨眼。上镜时应偏向更频繁一些（中位间隔约 2.8 秒）——眨眼太少在视频里会显得在盯视。参考： `useBlink.ts`.

**2.2 眼球跟踪（注视）**

与镜头的眼神接触是最强的单一真实感线索：

1. 每一帧，在头部局部空间中计算从角色眼睛位置指向相机的方向。
2. 将其转换为归一化的水平/垂直角度，并进行钳制（水平 ±30°，垂直 ±22°）——在头部必须转动之前，眼球本身有范围限制。
3. 驱动 **眼骨** ，如果骨骼绑定有它们；否则驱动 `eyeLook*` morphs（MetaHuman 风格的骨骼通过 morph 来旋转眼球——按约 0.65 缩放，因为这些形状编码的旋转量比 1.0 应该施加的更多）。
4. 使用较快的 lerp 过渡（眼球很快，约 0.2 因子），并让眼睑轻微跟随注视方向（eyeLook morph 也兼作眼睑跟随器）。
5. **说话时，锁定注视** ——说话者会看着听者的眼睛。在活跃说话期间，将扫视幅度渐变到零……
6. ……但不要变成死盯：在 **持续能量低谷** （短语之间的自然停顿——语音能量在几百毫秒内低于约 0.06），让微扫视以半幅度恢复。词句流动时完全锁定，在间隙中保留细微生命感。参考： `useEyeTracking.ts`.

**2.3 微扫视**

真实的眼睛从不会静止：每隔 0.6–2.6 秒会迅速跳到一个小的随机偏移（大多不超过范围的 30%，偶尔会重新定焦到正中心），并以利落的过渡（约 30/秒）叠加在镜头注视之上。由 2.2 中的语音门控控制。

#### 3. 头部与身体

**3.1 头部跟踪**

头部应朝向镜头，颈部共同分担转动：

1. 在角色空间中计算从头部到相机的偏航/俯仰。对于 *范围判定*，从 **骨骼根节点** （静态节点）来测量朝向——绝不要从动画脊柱上取值：脊柱会呼吸并摆动几度，任何由它驱动的阈值都会随着摆动翻转状态。
2. **在极限处保持，只有在盲区才释放。** 不要在 ±90° 边界处解除跟踪——观众在边界附近横移会让头部在完全跟踪/解除跟踪之间来回摆动（看起来像撞头）。只要观众还在可见范围内，就把目标钳制在最大转角；只有在角色后方很远的位置（约 max+20° … max+43°），即脸本来就看不见时，才淡回到动作片段姿态。
3. 按解剖结构分配：头部本身只承担最初的约**25°** 偏航； **其余由颈部承担** （剩余部分约 0.75）。如果只有 60° 的头部区间，那么对于侧面观众，头部会转到约 50°——接近关节极限的姿势，任何修正都会显得抽搐。
4. 用 **基于时间的** 平滑（`1 − exp(−rate·dt)`，rate ≈ 5/秒）并限制重定向速度（约 80°/秒）。逐帧 lerp 因子会把掉帧变成可见的阶梯。
5. 先对动画片段的头部姿态进行低通处理（τ ≈ 0.2 秒），再与动画混合，并保持相机锁定强度较高（静止约 0.9，说话约 0.95）——进入混合的原始片段运动正是头部晃动的来源。
6. 再在上面叠加生命感：缓慢的多正弦漂移（约 2°）、偶尔的注视切换（保持 1.5–4 秒，按时间平滑），以及在偏航转动中轻微的滚转“倾侧”。参考： `useHeadTracking.ts`.

**3.2 语音能量耦合——上半脸也会说话**

计算一个 0–1 的 **语音能量包络** ，来源于嘴部活动（下颌 + 嘴唇通道幅度，快起音 / 慢释放）。有两个系统会使用它：

* **眉毛/脸颊：** 随着能量抬升眉毛上提 / 脸颊上提通道（耦合约 0.9 到情绪叠层的上脸权重）。在提问时眉毛从不动的脸会显得像贴上去的。
* **强调点头：** 检测能量 **起始** ——一个快包络（τ ≈ 0.1 秒）上升到其自身的慢平均值（τ ≈ 0.55 秒）之上。在起始时让头部下压几度，然后释放。点头本身要做非对称后平滑（下沉 τ ≈ 0.09 秒，释放 τ ≈ 0.22 秒）——如果直接用起始信号驱动头部，就会追踪能量抖动，显得生硬。持续的响度必须产生 **不** 点头（不是持续鞠躬）；只有重读音节才会标记。

**3.3 情绪叠层**

保留一个低强度的静息情绪（细微微笑配方），它 **以 max 方式合并** 与语音帧合并，而不是覆盖它们，并在说话时抑制其嘴部区域通道，使其不会与 viseme 冲突。在社交节奏（挥手、问候）时短暂抬高微笑底值。参考： `useEmotion.ts`.

**3.4 在动画混合器之后应用**

如果身体动画片段也会影响脸部/头部，那么在混合器每帧更新之后，重新应用当前的口型同步帧和跟踪姿态，这样脸部始终以语音为准。

**3.5 呼吸与静止微动**

以约 13 次/分钟的节奏在胸部/脊柱上叠加 ±0.7° 的俯仰（不对称：吸气更快，呼气更长）可避免身体显得僵住。参考： `useBreathing.ts`。对于行走角色，二级物理（头发弹簧链、由加速度驱动的躯干倾斜、支撑脚 IK）会进一步叠加效果——见 `useHairPhysics.ts`, `useLocomotionDynamics.ts` 以及 `useFootLock.ts`.

#### 4. 时序与帧节奏鲁棒性

只有在稳定帧率下，真实感才能保住；人们报告的大多数“故障”其实都是帧节奏伪影：

* **说话结束检测：** 如果约 300 毫秒没有新帧，就视为说话结束并开始收敛——正式的结束事件即使晚到，也不会造成明显冻结。
* **中断：** `queue.consumeNormalizationSignal()` 在机器人被中断时会返回 true（仅一次）——将 morph 用 lerp 拉回到零。
* **将动画混合器步进钳制** （≤ 33 毫秒）。否则，主线程卡顿（UI 重新渲染、TTS 音频初始化、GC）会在一帧内让整个骨骼按卡顿的时间差前进——你的平滑随后会明显追着一个姿势瞬移。
* **速度除以真实经过时间**，绝不要除以被钳制的 dt——否则卡顿帧会把表观速度放大数倍，并惊吓状态机（脚锁、注视门控）。
* **每个缓动都必须基于时间** (`1 − exp(−dt/τ)`），绝不要使用逐帧常数，否则长帧会变成可见的台阶。
* **不要逐帧分配** 在热点路径中（包括调试日志）——GC 暂停会正好落在最显眼的地方。
* **叠加 UI 不能每帧重绘。** 聊天面板的动画视觉效果若以 60 fps 重绘，会迫使合成器不断在 WebGL 画布上重新混合面板，从而可能将帧率减半；将可视化降到 30 fps，空闲时停止绘制，并且绝不要在动画循环中驱动逐帧 SVG 滤镜（投影模糊）或 React setState。

#### 5. 角色准备

* **命名就是契约：** morph 必须与格式的通道名完全一致。在写代码之前，请先在 glTF 查看器中检查你的 GLB。
* **名称混淆：** 有些导出会丢失分隔符（`CTRL_expressionsjawOpen`）—— `bindAll`/`bindMesh` 会自动修复字典；如果你手动绑定，也请照做。
* **Lite 与 Full 骨骼：** MetaHuman-Lite 模型（103 个 morph）只使用 251 流中的命名子集——它能工作，但缺少眉毛/脸颊/眼部细节通道。主角角色请使用完整骨骼。
* **morph 骨骼上没有下颌/舌骨：** MetaHuman 风格的 glTF 导出通常完全由 morph 驱动——下颌运动完全存在于 `jawOpen`/`jawOpenExtreme`。不要为一个没有对象可驱动的下颌骨驱动器预留预算。
* **嘴部材质出厂时过亮：** 导出通常会让牙齿处于完整的 IBL 下。请把材质调整（§1.9）与动画调优一起纳入预算——“牙齿看起来不对”的一半问题都出在着色上。

***

### 缓冲调优

这个 `frames_buffer_duration` 选项控制服务器在随音频释放之前会累积多少秒的 blendshape。更高的值会提高口型同步精度，但会增加延迟。

```ts
blendshapeConfig: {
  format: 'arkit',
  frames_buffer_duration: 0.2, // 200 毫秒缓冲（默认：0.1）
}
```

#### 提前交付（提前发送契约）

使用 `deliver_chunks_ahead: true` 服务器会发送 **带索引的** NeuroSync 分块 *在……之前* 音频播放，因此客户端会持有一个真正的视觉缓冲区（实践中大约领先 2 秒），而不是追着语音跑。SDK 使用分块元数据（`fps`, `start_frame_index`, `response_id`, `neurosync_turn_id`, `epoch`）来对帧排序，并在对话被中断时丢弃已缓冲的视觉内容——使用 `client.blendshapeQueue` 以及 SDK 风格的播放器路径，这样按所有者范围的取消才能生效。

```ts
blendshapeConfig: {
  format: 'arkit',
  deliver_chunks_ahead: true, // 需主动启用
  output_fps: 60,
  frames_buffer_duration: 0.2,
}
```

它是 **需主动启用（默认关闭）** 在契约的服务器端仍在验证期间。当前服务器状态（已于 2026-07-27 验证，session id 已存档）：提前交付适用于 `arkit`, `cc4_extended` 以及 `visemes`; **通过以下方式请求它 `format: 'mha'` 目前会让 TTS/blendshape 流完全停滞** （文本回复仍会到达）——在 `mha` 之前保持该标志关闭，直到服务器发布修复。 `cc5_hd` 在任何模式下都不会传递帧。

***

### 示例：React Three Fiber

请在以下位置查看完整可运行示例： `examples/react-three-fiber` 其中演示了：

* MHA-251 blendshape 直接按名称映射到 MetaHuman 风格角色（`blendshapeConfig.format: 'mha'`，无需自定义映射器）
* 上文完整的自然感栈：流淡出包络、眼部通道跳过掩码、各通道增益、嘴部 L/R 对称化、在动画混合器之后重新应用
* 程序化眨眼、说话时锁定镜头的注视、头部跟踪、呼吸——与身体动画片段以及脚本化开场场景叠加
* 角色动作（`actionConfig` → `actionResponse` → 手势动画片段）以及驱动问候的叙事设计触发器


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/lipsync-and-blendshape/metahuman-limit-drivers-required-for-mha.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
