最近在弄Unity角色对话系统,被一个叫SALSA With RandomEyes的插件圈粉了。这东西说白了就是干一件事:让人物的嘴巴跟着语音动起来,配合随机眼球转动,整得跟真人说话似的。很多做独立游戏、虚拟主播、甚至数字人项目的朋友都在用这个方案。本文就把我从安装到落地的完整经验写出来,包括原理、配置步骤、参数调优和一些官方文档里没细说的坑。如果你正在为角色对话口型对不上、表情死板而头疼,这篇应该能帮你省下不少时间。
1. SALSA With RandomEyes整体设计与核心思路
1.1 这个插件到底解决什么问题
先聊一个几乎所有做角色对话的开发者都会遇到的心头痛:角色开口说话,但嘴巴要么一动不动,要么像机器人一样机械地张合。你辛辛苦苦做了好看的模型,一说话就露馅,沉浸感瞬间归零。
传统做法是手动K帧做口型动画,但一段五分钟的对话,几十句话,手动K帧能把人逼疯。而且音频一换,口型就废了,所有工作推倒重来。另一种做法是用第三方软件(比如Live2D、FaceFX)提前烘焙口型动画,但流程重、成本高、修改不灵活。
SALSA With RandomEyes这个插件走的是另一条路:实时驱动。它在运行时监听音频数据,通过分析音量、频率等特征,实时映射到角色的BlendShape(混合形状)或骨骼上,让嘴巴自然张合。同时RandomEyes模块负责眼球的随机运动、眨眼频率控制,让角色看起来像真的在思考和回应。这个方案最大的好处是:换任何一段语音进去,口型自动跟得上,完全不用重新K帧。
1.2 为什么选SALSA而不是其他方案
我对比过市面上几种主流实时口型方案,简单说下各自的优劣:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SALSA With RandomEyes | 配置快、支持语音驱动和文本驱动、自带眼球系统 | 老版本界面略旧、需要理解Viseme概念 | 中小团队、独立开发、实时对话 |
| 第三方烘焙工具 | 口型精度高、可精细调整 | 流程重、音频改动要重烘焙 | 过场动画、影视级CG |
| 自研算法 | 完全可控、无授权成本 | 开发周期数周到数月、需要音频处理背景 | 平台型产品、长期迭代项目 |
| FaceFX | 专业级方案、效果出色 | 收费高、学习曲线陡、Unity集成需要额外适配 | 大型3A项目 |
SALSA最打动我的一点是它的"低门槛高天花板":小白跟着教程半小时能跑通基础功能,进阶玩家又可以自己写表达式、对接表情系统、深度定制参数。而且它内置了RandomEyes,等于买一送一,眼睛和嘴巴一起搞定,省去了自己写眨眼逻辑的麻烦。
1.3 版本选择和授权注意事项
市面上能搜到的SALSA有两个常见版本:一个是比较老的SALSA with RandomEyes 1.x,另一个是Crazyminnow Studio官方一直在维护的新版(现在通常会作为Unity Asset Store上的付费资源)。如果你在Asset Store搜,注意区分:
- SALSA with RandomEyes:老牌插件,包含SALSA(口型)+ RandomEyes(眼球)两套系统,现在绑定的版本更新后还支持毛发、表情联动等,功能更多。
- SALSA LipSync Suite:官方后续推出的整合包,功能和授权有所不同。
建议直接购买官方Asset Store版本,授权清晰、更新及时,兼容新旧版Unity。网上有些旧版资源包,虽然能用,但代码老、不支持新Input System、也不兼容URP管线的后期处理,新手卡了半天发现是版本兼容问题,心态容易崩。
另外注意,SALSA支持的是**实时音频驱动(microphone live input或AudioSource playback)和文本转口型(SALSA Text)**两种核心模式。如果你想让角色朗读一段预先录好的语音文件,直接用AudioSource播放文件并交给SALSA解析就行;如果你想让角色对着聊天气泡里的文字说话,就得用SALSA Text模式。
2. SALSA核心细节解析:Viseme、BlendShape与参数调优
2.1 先搞懂Viseme(视位)是什么
很多人第一次用SALSA会一头雾水,因为界面里全是Viseme这个词。这东西听着学术,其实就是"发音时的口型形状"。英语里大约有14个基础Viseme(不同方案数量不同,SALSA常见的有AE、EE、IH、OH、OU、W、R、L、S、T、D、N、K、G、M、P、B等组合归类)。
可以这样理解:中文说话时我们的嘴唇大致也是几种固定状态的组合,比如发"a"音嘴巴竖起张开,发"o"音嘴巴收圆,发"i"音嘴角向两边拉开。SALSA做的就是:拿到一段音频,每帧分析它最像哪个Viseme,然后把你模型上对应的BlendShape(比如mouth_open、mouth_smile、mouth_pout)调到相应权重。
所以关键点在于:SALSA本身不产生口型数据,它只是把音频信号转换成一堆BlendShape或骨骼的驱动值。最终嘴型像不像,取决于你在模型上做(或者找到)了多少个能对应Viseme的BlendShape,以及你怎么把它们映射到SALSA里。
2.2 BlendShape映射的实操逻辑
大多数用Unity做的角色模型,脸部都会有几十个BlendShape(在SkinnedMeshRenderer的BlendShapes面板里能看到)。比如VRoid导出的模型、DAZ导出的模型、或者用Blender雕刻后导出的模型,一般都有类似mouth_open、mouth_sad、mouth_smile、jaw_forward、tongue_out之类的形变名称。
理想的映射方法有两种:
方法一:每个Viseme对应一个BlendShape(最简单)SALSA的Viseme面板里有一长串名字(AE, EE, IH, OH, OU, W, R, L, S, T, D, N, K, G, M, P, B),你只需要把模型里最接近该发音的形状拖拽到对应槽位。比如AE是"张嘴"音,你把mouth_open拖进去;P/B是闭嘴爆破音,你把mouth_press或者mouth_close拖进去。
方法二:一个Viseme对应多个BlendShape(推荐进阶)一个口型往往不只有一个BlendShape参与。比如发"W"音,嘴上要收圆,可能mouth_pucker和jaw_lower都要参与。SALSA支持对一个Viseme设置多个目标BlendShape并分配权重(属性面板里点加号添加元素,然后调整每个动捕目标的Weight权重)。实测下来,多BlendShape叠加效果会比单个硬邦邦的形变自然很多。
2.3 参数详解:从Threshold到Amplifier
SALSA面板上有几个关键参数直接决定口型表现,我逐个说下我的调参心得:
- Threshold(阈值):音频信号低于这个值就认为没声音,嘴巴完全闭合。数值太高会导致细声细气的时候嘴巴一动不动;太低会导致环境噪声也被当成说话,嘴巴乱动。建议先放在0.1~0.15,然后用一段安静的音频测试,观察Gizmo(Scene视图里那条黄色的音频振幅线)有没有在噪声段乱跳。
- Amplifier(增益放大器):把输入音量放大或缩小后映射给口型。如果角色说话时嘴巴张得太小气,就提高Amplifier(比如从1调到1.5);如果感觉嘴巴像吃了摇头丸一样疯狂开合就降低。
- Dampening(阻尼/平滑):控制口型变化的速度。玩家说话时嘴巴是一帧一帧瞬移还是平滑过渡,就看这个参数。SALSA里通常有两个Dampening(一个用于变化较快的辅音,一个用于变化的整体平滑度),默认值一般够用,但如果你的角色口型像痉挛,就把Dampening拉到0.5甚至更高。
- SALSA Coherence(相干性):控制整体口型平滑度的参数,越高越平滑,但过高会让辅音含糊。通常建议不要超过0.8,否则说话不利索。
- Gain:和Amplifier类似,但Gain更偏向于对低频信号的增益,如果低音重的角色(比如怪物、大叔)口型不够明显,试着加Gain。
这些参数没有"万能数值",因为每个模型的脸型、每段音频的音量、每个项目的审美都不一样。我的建议是:先用默认参数播放一段参考语音,然后逐个参数调整并实时观察Game视图里的角色表情,找到最自然的组合。调参这种事,快就是慢,得有点耐心。
2.4 数据驱动模式 vs Live模式该怎么选
SALSA支持两种主要的驱动来源:
- Audio Source模式:把一段AudioClip拖给SALSA组件,设置好对应AudioSource,播放时实时分析。
- Microphone Live模式:选择电脑麦克风作为输入,角色实时复读你的口型。适合直播、线下互动,但要注意回声和延迟。
- SALSA Text模式:不需要音频文件,直接把字符串文本投放给SALSA,它通过文本分析和语音合成器驱动口型。适合聊天框对话、剧情选择后的自动说话。
我强烈建议:如果你的项目是录好的语音对话,永远用Audio Source模式;如果是AI人格/在线对话,用SALSA Text模式;只有你做线下表演捕捉或直播互动才用Microphone模式。因为Microphone模式对噪音极其敏感,你会被环境音搞得怀疑人生。
3. 实操过程:从导入模型到跑通第一个说话角色
3.1 角色模型准备四步走
第1步:检查BlendShape在Unity里选中角色模型,在Inspector面板找到SkinnedMeshRenderer组件,展开BlendShapes下拉列表,确认模型里有嘴部相关形变。如果没有,得先回到建模软件里把BlendShape补充好再重新导入。一些商店模型经常不带BlendShape,这是个硬门槛。
- 给一个高中低频听得出来的提示:即使模型只有
mouth_open和mouth_smile两个BlendShape,也可以跑SALSA,只是效果会比较贫瘠。会有明显僵硬的"二值"感。建议至少具备:张嘴、闭嘴、笑、嘟嘴、唇角两侧拉开这五个基础形状。
第2步:清理模型比例角色的FaceRenderer(SALSA会自动查找SkinnedMeshRenderer)指向是否正确。如果模型导入时Scale不是1,先改回1,避免BlendShape权重受缩放影响。
第3步:导入SALSA插件官方Asset Store下载导入后,在Project窗口进入Crazyminnow/SALSA目录,将SALSA预设体(Prefab)拖到场景里,或者直接给角色对象添加SALSA LipSync组件。SALSA会自动查找角色身上的AudioSource和SkinnedMeshRenderer。
第4步:创建AudioSource并挂语音给角色添加AudioSource,把准备好的语音片段拖到AudioClip槽位,勾选Play On Awake先测试,也可以后面用代码触发播放。语音建议使用WAV(PCM16或者浮点格式),位深度太高或压缩太狠(MP3低码率)可能导致分析波形出现偏移。最稳的是44100Hz单声道WAV。
3.2 快速配置SALSA组件
在角色身上添加SALSA LipSync组件后,面板大概如下几个区域:
Audio Provider区域:
- Audio Source Selector:拖入角色身上的AudioSource
- Audio Source:会自动获取,确认绑定正确
- 如果是运行后用代码切换语音,可以写:
using UnityEngine; using CrazyMinnow.SALSA; public class PlayLipSyncAudio : MonoBehaviour { public AudioSource audioSource; public AudioClip clipToPlay; void Start() { var salsa = GetComponent<SalsaLipSync>(); // 替换音频并自动播放 salsa.SetAudioSource(audioSource); audioSource.clip = clipToPlay; audioSource.Play(); } }注意:SALSA运行时分析音频需要AudioSource处于正在播放的状态,如果AudioSource没播放,SALSA不会自动产生口型。
Viseme区域: 这里就是前面说的映射面板。系统会列出所有支持的Viseme名称,每个名称旁边有个空槽(Object)和权重(Weight)。你把对应的BlendShape拖进去就行。
高级设置区域: 在Advanced Options里能找到Dampening、Threshold、Amplifier、Gain等参数,以及是否使用”Lipsync“、是否在每个Viseme切换时触发事件回调等。事件回调是重点,后面做表情联动时要用到。
3.3 RandomEyes的基础配置
RandomEyes是SALSA插件包自带的另一个组件,主要负责眼球的随机运动、眨眼、瞳孔变化。添加组件后,它会自动寻找场景中角色的眼睛骨骼(Eye L/R)或者眼球的SkinnedMeshRenderer。
核心参数:
- Blink Duration / Blink Interval:眨眼持续时间和间隔。人一般3~6秒眨一次眼,默认值就挺自然;如果角色看起来像在抛媚眼,就把Interval调大。
- Look Target:可以让角色看向一个Transform,比如看向NPC、看向玩家、看向摄像机。
- Look/Blend Multiplier:眼球运动和多边形形变(比如上眼睑下移的形变)的强度。
- Random Eyes Movement: 眼睛随机转动频率和幅度,默认参数就可以。想要角色显得机灵,增加幅度;想要角色发呆,降低频率和幅度。
刚好配合SALSA的口型,同时开启眼动后,角色瞬间有了一种"活人"感。
3.4 跑通第一个效果:完整步骤清单
- 准备带BlendShape的角色预制体,放到场景。
- 给角色加AudioSource,拖入一段语音(WAV 44100Hz单声道)。
- 添加SALSA LipSync组件,绑定AudioSource。
- 在Viseme面板中,把嘴部相关BlendShape拖入对应槽位:
- AE → mouth_open
- EE → mouth_smile / mouth_corner_left+right
- OH → mouth_open 减小一点权重 / 如果有mouth_round更好
- OU → mouth_pucker
- M/P/B → mouth_press / mouth_close
- 调整Threshold为0.1,Dampening为0.3,Amplifier为1.0,先播放测试。
- 添加RandomEyes组件,绑定眼睛骨骼或BlendShape(如果有eye_blink),设置Look Target为摄像机。
- 点击Play,观察口型是否跟随语音节奏。不理想就按2.3的参数方法调。
跑通这个基础流程后,你会明显感觉到,角色说话时不再是"头一动不动、嘴一张一合",而是有口腔开合、圆唇、展唇的变化,配合眼球转动和眨眼,整体气质完全不同。
4. 表情联动与多人对话场景的扩展玩法
4.1 根据对话内容动态切换表情
热搜词里有"unity根据对话变化表情",这个需求SALSA可以很好地配合实现。思路很简单:SALSA负责嘴型和基础眼睛,你可以监听语音播放节点或者对话文本节点,动态设置角色额外的Emotion BlendShape(比如眉毛上挑、嘴角下压、瞪眼)。
SALSA本身有一个名为SalsaLipSync的Unity事件(OnVisemeChange事件),会在Viseme切换时触发。利用它,你可以在某些特定Viseme出现时触发表情调整。但更实用的是自己做一套对话事件系统:
using UnityEngine; using CrazyMinnow.SALSA; [System.Serializable] public class EmotionState { public string emotionName; public float browRaise; // 眉毛上挑权重 public float eyeWide; // 瞪眼权重 public float mouthSad; // 嘴角下压权重 } public class EmotionController : MonoBehaviour { public SkinnedMeshRenderer faceRenderer; public EmotionState[] emotions; public float smoothSpeed = 5f; private int browRaiseIndex; private int eyeWideIndex; private int mouthSadIndex; private float currentBrow, currentEye, currentMouth; private float targetBrow, targetEye, targetMouth; void Awake() { browRaiseIndex = faceRenderer.sharedMesh.GetBlendShapeIndex("brow_raise"); eyeWideIndex = faceRenderer.sharedMesh.GetBlendShapeIndex("eye_wide"); mouthSadIndex = faceRenderer.sharedMesh.GetBlendShapeIndex("mouth_sad"); } public void SetEmotion(string emotionName) { foreach (var e in emotions) { if (e.emotionName == emotionName) { targetBrow = e.browRaise; targetEye = e.eyeWide; targetMouth = e.mouthSad; break; } } } void Update() { currentBrow = Mathf.Lerp(currentBrow, targetBrow, Time.deltaTime * smoothSpeed); currentEye = Mathf.Lerp(currentEye, targetEye, Time.deltaTime * smoothSpeed); currentMouth = Mathf.Lerp(currentMouth, targetMouth, Time.deltaTime * smoothSpeed); if (browRaiseIndex >= 0) faceRenderer.SetBlendShapeWeight(browRaiseIndex, currentBrow); if (eyeWideIndex >= 0) faceRenderer.SetBlendShapeWeight(eyeWideIndex, currentEye); if (mouthSadIndex >= 0) faceRenderer.SetBlendShapeWeight(mouthSadIndex, currentMouth); } }这样结合对话流程控制角色情绪,比如说到愤怒内容时先设置SetEmotion("angry"),说话过程中嘴型由SALSA接管、情绪表情由你的系统接管,两层互不干扰。
4.2 多人对话应用中SALSA的架构建议
如果是剧情对话、多NPC同屏说话,建议不要给每个角色都挂独立实时分析组件(性能开销和材质实例化都会增加)。正确的是:
- 每个说话角色挂一个SALSA组件,但AudioSource可以指向同一个。SALSA能通过代码动态切换驱动源。
- 当前不说话的角色,用代码将SALSA的AudioSource指向一个空白Clip,或者调用
SalsaLipSync.Stop(),避免它跟着别人的语音乱动。 - 或者用SALSA Text模式给没配音的角色同步生成口型,这样在不播放任何音频时,角色嘴巴依然能配合文字显示开合。对于游戏里那种"只有字幕没有配音"的剧情,效果非常香。
4.3 阴影、UI,以及WebGL这些热搜词为什么关系不大
在热词里看到"unity阴影问题"、"unity world ui无遮挡"、"unity WebGL idbfs写入失败"等,乍看和SALSA无关,但做角色对话时确实容易踩界。简单提示两点:
- 如果角色用了URP高光材质,SALSA的BlendShape不受影响,但场景阴影错误会让面部看起来脏脏的。排查时先确认是否是光照贴图烘焙问题,而不是插件问题。
- WebGL发布时如果遇到IDBFS写入失败,那是浏览器文件系统权限问题,和SALSA的音频播放无关。可以尝试配置Unity WebGL的内存和文件系统设置,或者改用流式加载音频。总之别急着卸载SALSA,先定位问题层。
- 如果是"unity世界空间UI无遮挡",做对话系统时Word UI用来显示聊天气泡非常常见,但遮挡问题往往是Canvas组件上的Sorting Order或相机的Occlusion设置不对,和SALSA关系不大,单独排查Canvas即可。
5. 常见问题与排查技巧实录
5.1 口型不动或者只有嘴在颤抖
这个是我被问得最多的一个问题。原因通常有四种:
- AudioSource没播放:SALSA只分析正在播放的音频,确认AudioSource.isPlaying为true。
- BlendShape没映射正确:有的模型BlendShape名称和SALSA面板槽位对不上,你拖了不代表权重生效。去SkinnedMeshRenderer的BlendShape预览里手动拖一下权重,确认真实存在的形变。
- Threshold太高:语音轻的时候被阈值卡掉了。把Threshold调到0.05~0.1试试。
- 套用了禁止播放动画:角色身上的Animator如果有口型相关的动画,且权重为1,Animator的动画优先级会覆盖SALSA写入的BlendShape权重。解决办法是把口型相关动画放在一个专门Layer,权重设为0或者干脆用代码禁用相关骨骼/形变节点。这是SALSA和Animator混用时最常见的坑。
5.2 嘴巴张合严重滞后
如果你发现角色嘴巴比音频慢半拍,通常是因为Dampening值太高。Dampening相当于"惯性",太大时所有口型变化都被平滑掉了,听起来像在唱慢歌。先把Dampening降到0.2以下,同时检查项目里Role模型是否用了LateUpdate机制去写BlendShape。SALSA的默认执行顺序是Update,如果别的脚本在LateUpdate里也写了同一个BlendShape,可能在你写入后又被覆盖,半拍延迟就是这么来的。
5.3 怎么让角色说话时头也轻微晃动
SALSA只管嘴和眼,角色点头、摇头等头部微动作需要自己加。简单的方案是给角色加一个SalsaHead(老版本自带,新版本部分版本不会自动创建)或者自己写脚本:
using UnityEngine; public class HeadNod : MonoBehaviour { public float intensity = 2f; public float frequency = 1.2f; private Vector3 baseRotation; private AudioSource audioSource; void Start() { baseRotation = transform.localEulerAngles; audioSource = GetComponent<AudioSource>(); } void Update() { if (audioSource != null && audioSource.isPlaying) { float sample = 0f; // 用AudioSource的频谱数据做粗略的能量检测 float[] data = new float[64]; audioSource.GetSpectrumData(data, 0, FFTWindow.BlackmanHarris); for (int i = 0; i < data.Length; i++) sample += data[i]; sample /= data.Length; float sway = Mathf.Sin(Time.time * frequency * 2f * Mathf.PI) * sample * intensity; transform.localEulerAngles = baseRotation + new Vector3(sway * 0.3f, 0f, sway * 0.1f); } } }这只是最简方案,实际效果是头部随音量有细微摆动。如果想做更自然的节奏点头,不妨把AnimationCurve和音频振幅映射一下。
5.4 一个容易忽略的常见坑:多个SkinnedMeshRenderer
如果角色身体和头部是分离的(比如有些模型衣服是单独一个SkinnedMeshRenderer,头部脸是另一个),SALSA默认会找到第一个包含"面部"命名的SkinnedMeshRenderer,有时候命名的不是脸,就会找不到BlendShape。解决办法是在SALSA组件上手动指定FaceRenderer为正确的SkinnedMeshRenderer,同时确保所有脸部BlendShape都在同一个SkinnedMeshRenderer里。
5.5 性能优化:移动端或VR设备上的消耗
SALSA做的是音频频谱分析,计算量很小,主要开销在BlendShape权重写入。BlendShape写权重的性能消耗与网格顶点数相关,几千顶点的高模多写几个BlendShape可能就会在手机上顶不住。优化建议:
- 移动端使用低面数模型(5000~10000个三角面左右)。
- 减少同时使用BlendShape数量,用最少的形变组合达成有辨识度的口型。
- 如果VR一体机(比如PICO 4)上跑,建议把SALSA和RandomEyes的更新频率降到每秒30帧(写一个简单的update interval脚本),画面几乎看不出差异,但电量消耗能下降不少。
6. 一个完整的对话系统接入方案参考
最后放一个我在项目里用过的精简版对话系统方案,把SALSA和普通UI菜单、文本剧情串起来。核心思路是:UI显示对话文本,SALSA控制口型,代码控制情绪和音频切换:
using UnityEngine; using TMPro; using CrazyMinnow.SALSA; public class DialogueSystem : MonoBehaviour { public TextMeshProUGUI dialogueText; public AudioSource voiceSource; public SALSA salsa; public EmotionController emotionController; [System.Serializable] public class DialogueLine { public string speakerID; public string text; public AudioClip clip; public string emotion; // "neutral", "angry", "happy"... } public DialogueLine[] lines; private int currentLine = 0; public void PlayNextLine() { if (currentLine >= lines.Length) return; DialogueLine line = lines[currentLine]; dialogueText.text = line.text; voiceSource.clip = line.clip; voiceSource.Play(); emotionController.SetEmotion(line.emotion); salsa.SetAudioSource(voiceSource); // 确保SALSA跟着AudioSource currentLine++; } void Update() { if (Input.GetKeyDown(KeyCode.Space)) PlayNextLine(); } }这个小框架可以继续扩展:按角色区分语音音调、换成中文音频、对接对话树(Dialogue Tree)、按键快进、跳过后自动调整口型状态等。SALSA在这个架构里是纯"口型执行层",上层对话逻辑怎么折腾都不影响它,反过来它也不限制上层玩法。
我个人在实际操作中的体会是:SALSA With RandomEyes最值钱的不是它帮你免去了手动K帧,而是"实时驱动的思维模式"——你不需要知道角色下一句说什么,只要把音频给它,它就能自然开口。这放在以前,是想都不敢想的效率。对于独立开发者或者小团队来说,这个插件确实能省下至少一两周的动画工作量,而且改对话内容只需要换音频,不用动动画资源,迭代成本极低。如果你手头有做对话类游戏、虚拟人项目或者教学演示的需求,这个方案值得好好研究一下。