uLipSync
uLipSync是一款用于Unity的唇型同步资源。它具有以下特点:
- 利用Job System和Burst Compiler在任何操作系统上更快运行,无需使用原生插件。
- 可以校准以创建每个角色的配置文件。
- 支持实时分析和预烘焙处理。
- 预烘焙处理可以与Timeline集成。
- 预烘焙数据可以转换为AnimationClip
功能特性
唇型同步
[图片]
配置文件
[图片]
实时分析
[图片]
麦克风输入
[图片]
预烘焙
[图片]
Timeline
[图片]
AnimationClip
[图片]
纹理变化
[图片]
VRM支持
[图片]
WebGL支持
[图片]
安装
- Unity包
- 从Release页面下载最新的.unitypackage。
- 从Package Manager导入
Unity.Burst
和Unity.Mathematics
。
- Git URL (UPM)
- 在Package Manager中添加
https://github.com/hecomi/uLipSync.git#upm
。
- 在Package Manager中添加
- Scoped Registry (UPM)
- 在项目中添加一个作用域注册表。
- URL:
https://registry.npmjs.com
- 作用域:
com.hecomi
- URL:
- 在Package Manager中安装uLipSync。
- 在项目中添加一个作用域注册表。
使用方法
机制
当AudioSource
播放声音时,声音的缓冲会进入附加到同一GameObject的组件的OnAudioFilterRead()
方法。我们可以修改这个缓冲来应用混响等音效,同时由于我们知道正在播放的波形类型,我们也可以分析它来计算梅尔频率倒谱系数(MFCC),这代表了人类声道的特征。换句话说,如果计算得当,当前播放的波形是"a"时,你可以得到听起来像"啊"的参数,当前波形是"e"时,你可以得到听起来像"诶"的参数(除了元音,像"s"这样的辅音也可以被分析)。通过将这些参数与预先注册的每个"aieou"音素的参数进行比较,我们可以计算每个音素与当前声音的相似度,并利用这些信息来调整SkinnedMeshRenderer
的混合形状,实现准确的唇型同步。如果将麦克风输入馈送到AudioSource
,你也可以对当前声音进行唇型同步。
执行这种分析的组件是uLipSync
,包含音素参数的数据是Profile
,移动混合形状的组件是uLipSyncBlendShape
。我们还有一个uLipSyncMicrophone
资源,用于播放麦克风的音频。以下是它的示意图。
[图片]
设置
让我们使用Unity娘进行设置。示例场景是Samples / 01. Play AudioClip / 01-1. Play Audio Clip。如果你是从UPM安装的,请导入Samples / 00. Common sample(包含Unity的资源)。
放置Unity娘后,在任何要播放声音的游戏对象上添加AudioSource
组件,并设置一个AudioClip
来播放Unity娘的声音。
[图片]
首先,在同一GameObject上添加uLipSync
组件。现在,从列表中选择uLipSync-Profile-UnityChan
并将其分配给组件的Profile槽(如果分配其他内容,例如Male,唇型同步将无法正常工作)。
[图片]
接下来,设置混合形状以接收分析结果并移动它们。将uLipSyncBlendShape
添加到Unity娘的SkinnedMeshRenderer
的根部。选择目标混合形状MTH_DEF
,转到Blend Shapes > Phoneme - BlendShape Table,通过按+按钮添加7个项目:A、I、U、E、O、N和-("-"用于噪音)。然后如下图所示选择对应每个音素的混合形状。
[图片]
最后,要连接这两者,在uLipSync
组件中,转到*Parameters > On Lip Sync Updated (LipSyncInfo)并按+添加一个事件,然后将带有uLipSyncBlendShape
组件的游戏对象(或组件)拖放到写着None (Object)*的地方。在下拉列表中找到uLipSyncBlendShape
,并在其中选择OnLipSyncUpdate
。
[图片]
现在运行游戏时,Unity娘说话时会移动她的嘴巴。
调整唇型同步
可以在uLipSyncBlendShape
组件的Parameters中设置要识别的音量范围和嘴巴的响应速度。
[图片]
- Volume Min/Max (Log10)
- 设置要识别的最小和最大音量(闭合/最大开口)(对数10,所以0.1是-1,0.01是-2)。
- Smoothness
- 嘴巴的响应速度。
关于音量,你可以在uLipSync
组件的Runtime Information中看到当前、最大和最小音量的信息,所以尝试根据这些信息进行设置。
AudioSource位置
在某些情况下,你可能想将AudioSource
附加到嘴巴位置,而将uLipSync
附加到另一个GameObject。在这种情况下,可能有点麻烦,但你可以在与AudioSource
相同的GameObject上添加一个名为uLipSyncAudioSource
的组件,并在uLipSync Parameters > Audio Source Proxy中设置它。Samples / 03. AudioSource Proxy是一个示例场景。
[图片]
麦克风
如果你想使用麦克风作为输入,在与uLipSync
相同的GameObject上添加uLipSyncMicrophone
。这个组件将生成一个以麦克风输入为片段的AudioSource
。示例场景是Samples / 02-1. Mic Input。
[图片]
从Device中选择要用于输入的设备,如果勾选了Is Auto Start,它将自动启动。要启动和停止麦克风输入,在运行时按下如下所示UI中的Stop Mic / Start Mic按钮。
如果你想通过脚本控制,使用 uLipSync.MicUtil.GetDeviceList()
来识别要使用的麦克风,并将其 MicDevice.index
传递给该组件的 index
。然后调用 StartRecord()
开始录音或 StopRecord()
停止录音。
请注意,麦克风输入在 Unity 中的回放会比你实际说话稍有延迟。如果你想使用其他软件捕获的声音进行广播,请在 uLipSync
组件中将 Parameters > Output Sound Gain 设置为 0。如果 AudioSource
的音量设置为 0,传递给 OnAudioFilterRead()
的数据将会静音,无法进行分析。
在 uLipSync
组件中,转到 Profile > Profile 并从列表中选择一个配置文件(男性选择 Male,女性选择 Female 等),然后运行它。但是,由于配置文件未经个性化,默认配置文件的准确性可能不太理想。接下来,我们将看看如何创建与你自己声音匹配的校准数据。
校准
到目前为止,我们使用了样本 Profile
数据,但在本节中,让我们看看如何为其他声音(配音演员的数据或你自己的声音)创建调整后的数据。
创建配置文件
在 uLipSync
组件中点击 Profile > Profile > Create 按钮将在 Assets 目录的根目录创建数据并将其设置到组件中。你也可以通过右键点击 Project 窗口 > uLipSync > Profile 来创建。
接下来,在 Profile > MFCC > MFCCs 中注册你想要识别的音素。基本上,AIUEO 就足够了,但建议添加一个表示呼吸的音素("-" 或其他适当的字符)以防止呼吸输入。只要你注册的字符与 uLipSyncBlendShape
匹配,你可以使用任何字母、平假名、片假名等。
接下来,我们将对我们创建的每个音素进行校准。
使用麦克风输入进行校准
第一种方法是使用麦克风。应该将 uLipSyncMicrophone
添加到对象中。校准将在运行时进行,所以启动游戏以分析输入。按住每个音素右侧的 Calib 按钮,同时对着麦克风发出每个音素的声音,例如 A 发 "AAAAA",I 发 "IIIIII",依此类推。如果是噪音,不要说话或对着麦克风吹气。
如果你预先设置了 uLipSyncBlendShape
,观察嘴型如何逐渐匹配会很有趣。
如果你的说话方式略有不同,例如在自然声音和背景声音之间,你可以在 Profile
中注册多个相同名称的音素,并相应地进行调整。
使用 AudioClip 进行校准
下一种方法是使用音频数据进行校准。如果有一段说 "aaaaaaa" 或 "iiiiiii" 的声音,请循环播放并同样按下 Calib 按钮。然而,在大多数情况下,没有这样的音频,所以我们想通过剪辑现有音频中类似 "aaa" 或 "iii" 的部分并回放来实现校准。一个有用的组件是 uLipSyncCalibrationAudioPlayer
。这是一个在稍微交叉淡入淡出你想播放的部分时循环音频波形的组件。
通过拖动边界选择似乎在说 "aaaaa" 的部分,然后为每个音素按下 Calib 按钮,将 MFCC 注册到 Profile
中。
校准技巧
进行校准时,应注意以下几点:
- 在尽可能无噪音的环境中使用麦克风进行校准。
- 确保注册的 MFCC 尽可能恒定。
- 校准后,多次检查并重新校准不起作用的音素,或注册额外的音素。
- 你可以注册多个相同名称的音素,所以如果改变声音音调时不匹配,尝试注册更多音素。
- 如果音素不匹配,检查是否有错误的音素。
- 如果 MFCC 中有同名但完全不同颜色模式的音素,可能是错误的(相同音素应该有相似的模式)。
- 校准后检查时,折叠 Runtime Information。
- 编辑器每帧都会重绘,所以帧率可能会降到 60 以下。
预烘焙
到目前为止,我们已经看了运行时处理。现在我们将看看通过预计算生成数据。
机制
如果你有音频数据,你可以预先计算每帧将收到什么样的分析结果,所以我们将把它烘焙到一个名为 BakedData
的 ScriptableObject
中。在运行时,我们将使用名为 uLipSyncBakedDataPlayer
的组件来播放数据,而不是使用 uLipSync
在运行时分析数据。这个组件可以像 uLipSync
一样通过事件通知分析结果,所以你可以注册 uLipSyncBlendShape
来实现唇形同步。这个流程如下图所示。
设置
示例场景是 Samples / 05. Bake。你可以通过 Project 窗口的 Create > uLipSync > BakedData 创建一个 BakedData
。
在这里,指定校准过的 Profile
和一个 AudioClip
,然后点击 Bake 按钮来分析数据并完成数据生成。
如果一切正常,数据将如下所示。
将这些数据设置到 uLipSyncBakedDataPlayer
中。
现在你已经准备好播放了。如果你想在编辑器中再次检查,按下 Play 按钮,或者如果你想从另一个脚本播放,只需调用 Play()
。
参数
通过调整 Time Offset 滑块,你可以修改唇形同步的时机。在运行时分析中,无法在声音之前调整嘴巴的张开,但通过预计算,可以让嘴巴稍早打开,因此可以调整得更自然。
批量转换(1)
在某些情况下,你可能想一次性将所有角色配音 AudioClip
转换为 BakedData
。在这种情况下,请使用 Window > uLipSync > Baked Data Generator。
选择你想用于批量转换的 Profile,然后选择目标 AudioClips。如果 Input Type 是 List,直接注册 AudioClips(从 Project 窗口拖放多选很方便)。如果 Input Type 是 Directory,会打开一个文件对话框让你指定一个目录,它会自动列出该目录下的 AudioClips。
点击 Generate 按钮开始转换。
批量转换(2)
当你已经创建了数据,你可能想重新审视校准并更改配置文件。在这种情况下,每个 Profile
的 Baked Data 标签中有一个 Reconvert 按钮,它使用 Profile
转换所有数据。
时间轴
您可以在时间轴中为uLipSync添加特殊轨道和剪辑。然后我们需要绑定哪些对象将使用时间轴中的数据进行移动。为此,引入了一个名为uLipSyncTimelineEvent
的组件,它接收播放信息并通知uLipSyncBlendShape
。流程如下图所示。
设置
在时间轴的轨道区域右击,从uLipSync.Timeline > uLipSync Track添加专用轨道。然后在剪辑区域右击,从Add From Baked Data添加剪辑。您也可以直接将BakedData
拖放到此区域。
选择剪辑时,您将在检查器中看到以下UI,您可以在此替换BakedData
。
接下来,向某个游戏对象添加uLipSyncTimelineEvent
,然后添加绑定以便可以播放唇形同步。此时,在*On Lip Sync Update (LipSyncInfo)*中注册uLipSyncBlendShape
。
然后点击带有PlayableDirector
的游戏对象,将游戏对象拖放到时间轴窗口中uLipSyncTrack
的绑定槽中。
现在唇形同步信息将发送到uLipSyncTimelineEvent
,并与uLipSyncBlendShape
建立连接。在编辑时也可以进行播放,因此您可以与动画和声音一起调整。
时间轴设置助手
Window > uLipSync > Timeline Setup Helper
此工具自动创建与AudioTrack中注册的剪辑相对应的BakedData
,并将其注册到uLipSync Track中。
动画烘焙
您还可以将预先计算的唇形同步数据BakedData
转换为AnimationClip
。将其保存为动画可以轻松地与其他动画组合,将其集成到现有工作流程中,并通过移动关键帧later进行调整。示例场景是Samples / 07. Animation Bake。
设置
选择Window > uLipSync > Animation Clip Generator以打开uLipSync Animation Clip Generator窗口。
要运行动画烘焙,您需要打开已设置uLipSyncBlendShape
组件的场景。然后,请将场景中的组件设置到此窗口的字段中。
- Animator
- 选择场景中的
Animator
组件。 AnimationClip
将从此Animator开始的层次结构中创建。
- 选择场景中的
- Blend Shape
- 选择场景中存在的
uLipSyncBlendShape
组件。
- 选择场景中存在的
- Baked Data List
- 选择要转换为
AnimationClip
的BakedData资产。
- 选择要转换为
- Sample Frame Rate
- 指定要添加关键帧的采样率(fps)。
- Threshold
- 只有当权重变化超过此值时才会添加关键帧。
- 权重的最大值为100,因此10表示当权重变化10%时。
- Output Directory
- 指定要输出烘焙动画剪辑的目录。
- 如果目录为空,则在Assets(根目录)下创建。
以下图片是一个示例设置。
将Threshold从0、10和20变化,您将得到以下结果。
纹理
uLipSyncTexture
允许您根据识别的音素更改纹理和UV。Samples / 08. Texture是一个示例场景。
- Renderer
- 指定要更新的材质的
Renderer
。
- 指定要更新的材质的
- Parameters
- Min Volume
- 更新的最小音量值(log10)。
- Min Duration
- 保持嘴部使用相同纹理/uv的最短时间。
- Min Volume
- Textures
- 在这里您可以选择要分配的纹理
- Phoneme
- 输入
Profile
中注册的音素(例如"A"、"I")。 - 空字符串("")将被视为没有音频输入。
- 输入
- Texture
- 指定要更改的纹理。
- 如果未指定,将使用材质中设置的初始纹理。
- UV Scale
- UV缩放。对于平铺纹理,指定此值。
- UV Offset
- UV偏移。对于平铺纹理,指定此值。
动画器
uLipSyncAnimator
可用于使用AnimatorController
进行唇形同步。创建一个仅应用于嘴部的Avatar Mask的Layer,如下所示,并设置一个Blend Tree,使每个嘴型通过参数移动。
然后将音素和相应的AnimatorController参数设置到uLipSyncAnimator
中,如下所示。
示例场景是Samples / 09. Animator。
VRM支持
VRM是一种独立于平台的文件格式,专为3D角色和头像设计。在VRM 0.X中,通过VRMBlendShapeProxy
控制混合形状,而在1.0版本中,混合形状被抽象为Expression并通过VRM10ObjectExpression
控制。
- https://virtualcast.jp/wiki/vrm/setting/blendshap
- https://vrm.dev/en/univrm1/vrm1_tutorial/expression
VRM 0.X
使用uLipSyncBlendShape
时,直接控制了SkinnedMeshRenderer
中的混合形状,但有一个名为uLipSyncBlendShapeVRM
的修改组件,它改为控制VRMBlendShapeProxy
。
VRM 1.0
通过使用uLipSyncExpressionVRM
,您可以控制VRM10ObjectExpression
。
示例
有关更多详细信息,请参阅Samples / VRM。在此示例中,使用uLipSyncExpressionVRM
进行VRM 1.0的设置。
脚本定义符号
从.unitypackage安装VRM包时,需要手动添加Scripting Define Symbols。对于VRM 0.X,添加USE_VRM0X
,对于VRM 1.0,添加USE_VRM10
。如果通过Package Manager添加包,这些符号会自动添加。
运行时设置
如果你动态生成模型,需要自行设置和连接 uLipSync
和 uLipSyncBlendShape
。10. Runtime Setup 中包含了一个示例。你将动态地将这些组件附加到目标对象并按如下方式设置:
[System.Serializable]
public class PhonemeBlendShapeInfo
{
public string phoneme;
public string blendShape;
}
public GameObject target;
public uLipSync.Profile profile;
public string skinnedMeshRendererName = "MTH_DEF";
public List<PhonemeBlendShapeInfo> phonemeBlendShapeTable = new List<PhonemeBlendShapeInfo>();
uLipSync.uLipSync _lipsync;
uLipSync.uLipSyncBlendShape _blendShape;
void Start()
{
// 设置 uLipSyncBlendShape
var targetTform = uLipSync.Util.FindChildRecursively(target.transform, skinnedMeshRendererName);
var smr = targetTform.GetComponent<SkinnedMeshRenderer>();
_blendShape = target.AddComponent<uLipSync.uLipSyncBlendShape>();
_blendShape.skinnedMeshRenderer = smr;
foreach (var info in phonemeBlendShapeTable)
{
_blendShape.AddBlendShape(info.phoneme, info.blendShape);
}
// 设置 uLipSync 并将其与 uLipSyncBlendShape 连接
_lipsync = target.AddComponent<uLipSync.uLipSync>();
_lipsync.profile = profile;
_lipsync.onLipSyncUpdate.AddListener(_blendShape.OnLipSyncUpdate);
}
然后将此组件附加到某个 GameObject 上,提前准备好必要的信息并创建预制体或其他内容。该示例包括常规 SkinnedMeshRenderer
的设置和 VRM 1.0 的设置。
用户界面
当你想在运行时创建、加载、保存 Profile
,或添加音素并进行校准时,你需要一个用户界面。11. UI 添加了一个简单的示例。通过修改它,你可以创建自己的自定义用户界面。
提示
自定义事件
uLipSyncBlendShape
用于 3D 模型,uLipSyncTexture
用于 2D 纹理。但如果你想做一些不同的事情,可以编写自己的组件来支持它们。准备一个提供接收 uLipSync.LipSyncInfo
函数的组件,并将其注册到 uLipSync
或 uLipSyncBakedDataPlayer
的 OnLipSyncUpdate(LipSyncInfo)。
例如,以下是一个简单脚本的示例,它将识别结果输出到 Debug.Log()
。
using UnityEngine;
using uLipSync;
public class DebugPrintLipSyncInfo : MonoBehaviour
{
public void OnLipSyncUpdate(LipSyncInfo info)
{
if (!isActiveAndEnabled) return;
if (info.volume < Mathf.Epsilon) return;
Debug.LogFormat($"PHONEME: {info.phoneme}, VOL: {info.volume} ");
}
}
LipSyncInfo
是一个具有以下成员的结构。
public struct LipSyncInfo
{
public string phoneme; // 主要音素
public float volume; // 归一化音量(0 ~ 1)
public float rawVolume; // 原始音量
public Dictionary<string, float> phonemeRatios; // 包含音素及其比率的表
}
导入/导出 JSON
有一个功能可以将配置文件保存到 JSON 或从 JSON 加载。在编辑器中,从 Import / Export JSON 标签指定要保存或加载的 JSON,然后点击 Import 或 Export 按钮。
如果你想在代码中执行此操作,可以使用以下代码。
var lipSync = GetComponent<uLipSync>();
var profile = lipSync.profile;
// 导出
profile.Export(path);
// 导入
profile.Import(path);
运行时校准
如果你想在运行时执行校准,可以通过使用 uLipSync.RequestCalibration(int index)
向 uLipSync
发出请求来实现,如下所示。从当前播放的声音计算出的 MFCC 将被设置到指定的音素。
lipSync = GetComponent<uLipSync>();
for (int i = 0; i < lipSync.profile.mfccs.Count; ++i)
{
var key = (KeyCode)((int)(KeyCode.Alpha1) + i);
if (Input.GetKey(key)) lipSync.RequestCalibration(i);
}
请参考 CalibrationByKeyboardInput.cs 了解它的实际工作原理。另外,最好在构建应用程序后将配置文件保存并恢复为 JSON,因为对 ScriptableObject
的更改无法保存。
更新方法
Update Method 可用于调整使用 uLipSyncBlendShape
更新混合形状的时机。每个参数的描述如下。
方法 | 时机 |
---|---|
LateUpdate | LateUpdate(默认) |
Update | Update |
FixedUpdate | FixedUpdate |
LipSyncUpdateEvent | 收到 LipSyncUpdateEvent 后立即 |
External | 从外部脚本更新(ApplyBlendShapes() ) |
WebGL
当将 WebGL 设置为目标平台时,会出现一个小的 UI 添加。
在 WebGL 中,由于自动播放策略,除非与页面内容进行交互(如点击),否则音频不会播放。然而,由于 Unity 内部仍在播放声音,这会导致原本应该从一开始就播放的音频不同步。启用 Auto Audio Sync On Web GL 将在用户交互发生时纠正这种差异。
Audio Sync Offset Time 可用于调整唇形同步延迟的时机。在内部,由于 WebGL 中无法使用 OnAudioFilterRead()
,因此使用 AudioClip.GetData()
代替。这种方法利用了略微调整正在检索的缓冲区位置的能力。
目前,Unity 不支持 WebGL 的 JobSystem 和 Burst,这意味着性能不是最佳的。对于不需要实时分析的情况,建议预先烘焙数据。
Mac 构建
在 Mac 上构建时,你可能会遇到以下错误。
Building Library/Bee/artifacts/MacStandalonePlayerBuildProgram/Features/uLipSync.Runtime-FeaturesChecked.txt failed with output: Failed because this command failed to write the following output files: Library/Bee/artifacts/MacStandalonePlayerBuildProgram/Features/uLipSync.Runtime-FeaturesChecked.txt
这可能与麦克风访问代码有关,可以通过在 Project Settings > Player's Other Settings > Mac Configuration > Microphone Usage Description 中写入一些内容来修复。
从 v2 到 v3 的过渡
从 v3.0.0 开始,MFCC 的值已被修正为更准确的值。因此,如果你从 v2 过渡到 v3,你需要重新校准并创建一个新的 Profile
。
第三方许可
Unity-chan
示例包含 Unity-chan 资产。
© Unity Technologies Japan/UCL