Lyra技术解析 - 核心音频
音频的本质是信号,音频调制的本质是信号处理。
本文内容基于Unreal Engine 5.5.4
如果我犯了错误,请在下面评论并帮助未来的读者!本文中文翻译由AI机翻,可能不够准确或产生一定的阅读困难。
这是一系列关于我从Epic的Lyra项目中学到的知识笔记。该项目声称展示了当前虚幻引擎框架下的最佳实践。其中有些内容是我之前不了解的,有些则已经知晓,但认为仍然值得记录。
我并不是音频专家,所以这篇文章更多是我从Lyra项目中学习到的内容总结,主要是为了加深自己的理解。某些地方很可能有误,请谨慎参考。
Unreal 5中的音频结构
与我们在UE4中常见的音频系统不同,UE4只有一些基础功能,比如sound cues、sound classes和sound mix,并没有真正的DSP图。大部分复杂的音频处理都是通过事件发送到其他音频引擎(如Wwise、FMOD等)来完成的。而在UE5中,全新的音频引擎是内置的,并且拥有一个完整的DSP图,称为MetaSound。音频系统变得更加灵活和强大,能够实现复杂的音频处理和操作。
音频信号的整体流程如下:
Sound Source生成干声信号Sound Wave、Sound Cue、MetaSound等会产生原始音频信号
- 混音系统将干声信号混合为湿声信号
Sound Class、Sound Mix、MetaSound Patch完成第一阶段的处理,通常我们会将音频资源分到不同的类别,然后类别会映射到Submix类Submix、Submix Effect Chains等决定音频信号如何被处理和混合
- 调制
Control Bus、Control Bus Mix、Parameter Patch用于调制我们已经混合好的湿声信号
- 输出到输出设备
- 最终,
Main Submix会将最终音频信号输出到输出设备,比如扬声器或耳机
- 最终,
声音源(Sound Source)
声音源是音频信号的起点。在Lyra中有三种类型的声音源:
SoundWave: 最基础的声音源,是原始音频文件,存储为16位WAV格式。可以直接播放,但没有任何处理或操作SoundCue: 更高级的声音源,是多个SoundWave资源的容器。可以实现更复杂的音频处理,比如随机、循环、淡入淡出等。MetaSound Source: 最先进的声音源,是一个完全可编程的音频引擎,允许实现复杂的音频处理。可以用来创建自定义音效,比如混响、延迟、失真等。
这里还有一点:
MetaSounds Patch
MetaSound Source可以包含很多节点,很像材质编辑器的图表。作为一个完整的DSP引擎,可以对音频信号做大量逻辑处理。因此,把一些常用逻辑预设为“库”,需要时直接插入图表是很自然的做法。这就是MetaSound Patch的作用——它是可以在不同MetaSound Source中复用的节点集合,有点类似于音频处理的函数库。
Lyra中现有的MetaSound Patch包括:
lib_StereoBalance- 有一个
StereoPanner节点,可以将音频信号左右声道平衡
- 有一个
lib_DovetailClip- 将新音频信号与正在播放的音频进行混合处理,最终通过立体声平衡节点输出
- 这也意味着:
metasound节点与材质节点不同,它具有状态存储功能。只要metasound存在,节点就会持续维护内存中的变量状态
lib_DovetailClipFromArray- 支持从数组中选择音频波形,并应用
DovetailClip效果
- 支持从数组中选择音频波形,并应用
lib_RandInterpTo- 渐进式插值过渡到一个随机值
lib_RandPanStereo- 为左右声道分别生成随机声像定位值
lib_TriggerAfter- 在计数达到n次后触发一次
lib_TriggerEvery- 每n次计数触发,但仅在首次计数时返回true。例如设置重置计数为4时:
- true(计数=1)
- false(计数=2)
- false(计数=3)
- false(计数=4)
- true(计数=1,因已重置)
- 以此类推
- 注意计数值与实际数值不同。假设使用初始值0、步长1的计数器:
- 数值=0(计数1)
- 数值=1(计数2)
- 数值=2(计数3)
- 数值=3(计数4,触发重置)
- 数值=0(计数1)
- …
- 每n次计数触发,但仅在首次计数时返回true。例如设置重置计数为4时:
lib_TriggerModulo- 当计数器数值达到设定值n的模时为true
lib_TriggerStopAfter- 仅在n次计数内触发,超过后停止(如n=4时仅触发4次)
lib_Whizby- 模拟子弹近距离飞过的呼啸声效果。触发时随机播放带音高变化的接近音效,延迟片刻后播放随机选择的远离音效
MS_Graph_RandomPitch_Stereo- 为输入音频波形添加随机音高变化后输出立体声
MS_Graph_TriggerDelayPitchShift_Mono- 为输入音频波形添加随机延迟和音高变化后输出单声道
mx_PlayAmbientElement- 从指定数组随机选择环境音效,添加初始延迟后经立体声平衡输出
mx_PlayAmbientChord- 混合两个
mx_PlayAmbientElement的输出(是的直接相加,根据相位关系会产生音量增减)
- 混合两个
mx_Stingers- 当音乐系统中触发
OnStingerPositive时,会暴力混合5种类型的音效:- bass 贝斯
- perc-deep 重型打击乐
- perc-light 轻型打击乐
- short-pad 短垫音
- wet-lead 主奏湿音
- 当音乐系统中触发
sfx_BaseLayer_Interactable_Pad_nl_meta- 仅是
sfx_Random_nl_meta的封装。你可能会问为何不直接让后者成为预设模块?别问我,问Epic sfx_Random_nl_meta会按配置权重和增益播放数组中的随机音效- 调用此节点会触发代码断言提示库已损坏…显然不是最佳实践
- 仅是
Mixing
理清音源后,接下来是混音环节。混音本质是将多个音频信号合并为单一输出,但实际远不止简单相加(MixAB = A + B),而是通过MixAB = f(A) + f(B)实现。这个过程包括对原始信号施加混响、包络、均衡器、高低通滤波等处理生成f(x),再以和谐方式组合——这正是混音既耗时又复杂的原因。
虽然大部分混音功能可通过metasound的DSP图表实现,但还有几个辅助系统:
Sound Class
这是UE4遗留功能,音源通常会被分配到特定类别(如音乐类、音效类等)。在UE4中主要配合Sound Mix实现被动Ducking(即根据B通道音量自动降低A通道音量,如播放音效时压低背景音乐)。UE5中仍支持此功能,但新增了两个更强大的特性:
- 将信号发送到
Submix - 动态调制信号
本节先聚焦Submix功能
注意:使用被动
Ducking的Sound Mix会直接修改原始信号。这意味着Sound Mix优先级高于Submix——如果已通过Sound Mix将音量降至-60分贝,此时即使发送100%信号到Submix,接收到的也只是近乎无声的平直信号
Submix
Submix 是一种信号处理概念。想象一下,我们有武器音效、脚步声、环境声和音乐的原文件——它们通常只是原始的 wav 文件,随后会被封装到 SoundCue 或 metasound 类中进行单独处理(即“逐音效处理”)。但如果我们能将这些音效分组,并为它们统一添加共享效果(比如 EQ 或其它全局处理),会非常有用。这就是 Submix 的用武之地:任何音源或音效类别都可以部分或全部发送到某个 Submix,而该 Submix 会应用一条 SubmixEffectChain 效果链。不同的 Submix 可以协同运作,最终汇入 MainSubmix 并输出。
需要注意的是,Submix 仅处理原始信号的副本。例如,如果有一段音乐正在播放,我们将其 100% 发送到一个 Mute Submix,它仍然能被听到——因为我们听到的是原始音乐与“无声”混合的结果,本质上仍是原音。
这种反直觉的行为可能导致问题,因此通常我们会选择只输出处理后的湿信号(wet signal),同时静音所有干信号(dry sound)。(具体取决于项目需求,有时也会将部分干音发送到混响中。)但在 Lyra 中,所有干信号均被静音。
如前所述,我们也可以将部分干信号发送到 Submix。比如发送 0.2 表示取原始波形信号振幅的 20%,对其应用 EQ 等效果,最终在播放时混合。
FLyraSubmixEffectChainMap
这里需要稍微涉及源码:这个结构体将 Submix 类与 USoundEffectSubmixPreset 绑定,便于定义每个 Submix 应应用哪些预设效果。之所以称为 SubmixEffectChain(效果链),是因为它是一个数组,意味着单个 Submix 可以叠加多个效果(如 EQ、LPF 等)。
1
2
3
4
5
6
7
8
9
10
11
12
USTRUCT()
struct LYRAGAME_API FLyraSubmixEffectChainMap
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, meta = (AllowedClasses = "/Script/Engine.SoundSubmix"))
TSoftObjectPtr<USoundSubmix> Submix = nullptr;
UPROPERTY(EditAnywhere, meta = (AllowedClasses = "/Script/Engine.SoundEffectSubmixPreset"))
TArray<TSoftObjectPtr<USoundEffectSubmixPreset>> SubmixEffectChain;
};
HDR 与 LDR
以 HDR(高动态范围)和 LDR(低动态范围)为例:玩家的音频输出设备可能千差万别(高端设备、廉价设备、耳机或电视),且个人偏好或夜间模式等需求各异。为此,AudioSettings 类提供了 HDRAudioSubmixEffectChain 和 LDRAudioSubmixEffectChain 两个属性(均为 FLyraSubmixEffectChainMap 数组),并需要一套机制让玩家通过设置 UI 切换。
实现逻辑分为两部分:
- 用户配置:提供
UI让玩家选择HDR或LDR输出模式。 - 音频处理:对所有音效应用对应的“后处理”效果链(
SubmixEffectChain)。
第二部分正是通过动态操作 Submix 实现的。
动态 Submix 效果
但问题来了:既然 Submix 本身可直接配置 SubmixEffectChain,为何还要封装成 FLyraSubmixEffectChainMap?
答案在于:该结构体实际仅用于 HDRAudioSubmixEffectChain 和 LDRAudioSubmixEffectChain,其他 Submix 的效果链是直接在资产中静态配置的。
如上图所示,只有 MainSubmix 会进一步动态处理——因为 HDR/LDR 的选择是运行时决定的。
HDR 效果
HDR 效果属于压缩器类型,其逻辑如下:
- 输入增益:降低
10db以预留动态余量。 - 阈值:当信号超过
-6db时触发压缩器,采用6:1压缩比(输入每增加6db,输出仅增加1db)。 - 平滑处理:通过
Knee参数使阈值附近过渡自然,Lookahead则通过短暂延迟预判信号以避免突变。 - 侧链(
SideChain)输入:当侧链信号超过阈值时,可对当前输出实施增益或截断(类似Ducking效果)。
这种设计旨在保留尽可能多的动态范围,仅平滑压制过响部分,适合追求电影化体验的场景。
当声音作为输入增益降低10db后,系统会使用平均模式检测峰值音量(获取左右声道输入的平均值)。在绕过-6db阈值后,我们将应用6:1的压缩比,这意味着输入源每增加6db,输出源仅增加1db。如果这种处理让声音听起来被截断得太生硬,这时候knee(软拐点)和lookahead(预判)功能就派上用场了——lookahead能让我们稍微延迟声音信号,从而监测”未来”的信号并提前处理;而knee则意味着虽然我们以-6db作为阈值,但在+-knee的数值范围内,压缩器会渐进式地介入,让过渡更平滑。
和常规包络效果器类似,压缩器也有启动时间(attack)和释放时间(release)。它还具有侧链输入功能——当侧链输入信号超过阈值时,系统会对当前输出信号进行增益或截断处理,就像闪避效果(ducking effect)那样。
LDR 效果
与HDR非常相似,但这次是限制器(limiter)类型,意味着当声音超过阈值时会直接截断。背后的逻辑是:我们要不惜一切代价确保声音不超过特定电平,避免削波(clip)或失真。虽然有时会让声音听起来略显平淡,但目的是尽可能保持声音干净一致。通常用于电视或扬声器系统场景。
可用子混音系统
上图是Lyra中的子混音结构,所有Submix最终都会路由到MainSubmix:
UISubmixSFXSubmixMusicSubmixSendEffectSubmixReverbSubmixVoiceSubmixEarlyReflectionSubmix
声音衰减
声音衰减(Sound Attenuation)用于模拟声音在环境中的传播特性,可表现声源的距离和方向变化,也能模拟墙壁等障碍物对声音的影响。
Sound Cue或Meta Sound音源会引用Sound Attenuation Presets(声音衰减预设)
Attenuation Presets
衰减预设通常包含以下参数:
Volume- 声音随距离增加而衰减的曲线
Spatialization- 通过左右声道平衡实现3D空间中的声源定位
Air Absorption- 根据距离和声音频率,高频部分会被空气更多吸收
Focus- 摄像机焦点内的声音会有更高优先级和突出表现
Reverb- 受环境混响影响(如房间尺寸/形状、墙面材质等)
Occlusion- 受墙壁等障碍物遮挡影响,材质吸收会导致高频衰减
Priority- 决定声音在环境中与其他音源的混合权重
Submix- 可将声音发送到指定
Submix进行后续处理
- 可将声音发送到指定
ITD 声源空间化设置
Attenuation Presets可以引用ITD空间化设置,这是通过声源距离和方向来控制声道平衡算法的进阶方案。可用于模拟3D空间中的移动声源(如车辆经过或人物走动)。
Concurrency 并发控制
Sound Source可引用Concurrency Settings来控制同时播放的音源数量,超限时会切断某些声音。适合模拟人群或繁忙街道等多声源场景。
默认策略是最远优先→最早优先,即优先切断距离最远的声源,距离相同时则切断最早播放的声音。其他策略包括:
- 阻止新增
- 停止最早
- 最远优先→阻止新增
- 最低优先级
- 最弱音量
- 最低优先级→阻止新增
Effects
Lyra 内置了几种音频特效,它们都属于 SubmixEffect 类型,用于 Submix 中的 SubmixEffectChain。这些特效可以对音频信号进行处理,比如添加 混响(reverb)、延迟(delay) 或 失真(distortion),从而在游戏中营造特定的氛围或增强沉浸感。
卷积混响(Convolution Reverb)
Convolution Reverb 是一种高级混响效果,它使用 IR(脉冲响应) 资源。这种资源由 WAV 文件生成,通过分析录制的”啪”声波形,捕捉空间特征(包括材质、早期反射、能量空气吸收等),从而指导输入声源模拟出该空间中的声音行为。
官方文档:Convolution Reverb
不过有趣的是,Lyra 虽然将 Convolution Reverb 用在 ReverbSubmix 中,但这个子混音并未被实际调用。或许它是为游戏功能插件(Game Feature plugins)预留的内容。
创建脉冲响应(Audio Impulse Response)
新建 Audio Impulse Response 资源很简单:在内容浏览器中右键点击录制好的 WAV 文件,选择 Create Audio Impulse Response 即可自动生成。
注意:WAV文件必须是立体声,且理想情况下是1-2秒的”啪”声。文件需要包含清晰的全能量峰值,随后衰减至静默——这些信息将用于计算混响的脉冲响应。
动态处理(Dynamics Process)
这部分内容已在 HDR 和 LDR 章节中涵盖。
滤波器特效(Effect Filter)
滤波器特效用于根据特定频率范围处理音频信号。
可选类型包括:
- 低通滤波(low pass)
- 允许低频通过,截断高频。适合制造闷响或低沉隆隆声
- 高通滤波(high pass)
- 允许高频通过,截断低频。适合制造明亮或尖锐音效
- 带通滤波(band pass)
- 仅允许特定频段通过。适合制造特殊音效或共振效果
- 带阻滤波(band stop)
- 截断特定频段,允许其他频率通过。用途与带通滤波类似
分拍延迟(Effect Tap Delay)
分拍延迟特效可为音频信号添加延迟效果,用于制造回声或混响。Lyra 在 TAP_EarlyReflection 中配置了8个分拍(默认静音),WeaponAudioFunctions 会根据射击事件的射线检测结果动态更新这些分拍值。
早期反射(Early Reflection)
Lyra 使用 SubmixEffectTapDelay 和 SubmixEffectFilter 构建早期反射系统。早期反射是指声音经短延迟后到达听者的现象,能增强空间纵深感。每次开火时,系统会发射一系列射线(含若干反弹射线),根据射线传播距离和能量吸收判定空间特征。
两个滤波器的参数设置为:
- 高通截止频率 = 300Hz
- 低通截止频率 = 10000Hz
相当于将声音限制在 300Hz 至 10kHz 的人声频率范围内。
多频段压缩器(Multiband Compressor)
项目中还存在一个未被引用的 low multiband compressor,它可以根据不同频段压缩音频信号。这个 多频段压缩器 包含4个频段(2.5kHz 至 20kHz),每个频段可独立设置 阈值(threshold)、压缩比(ratio)、启动时间(attack) 和 释放时间(release)。
调制系统(Modulation)
现在正好可以回顾已学内容。整个音频流程可简述为:
- 用
sound class对声音分类(如music、sfx、footsteps等) - 通过
sound mix实现被动Ducking(Unreal 4的传统方案,Lyra未实际使用) - 将声音发送至
Submix进行深度处理 - 每个声音的音量由 参数补丁类(
Parameter Patch,PP) 进一步调制 PP类会将多个Control Bus的输入值相乘,输出最终值供sound class读取Control Bus本质是包含参数的调音台推子,关键问题在于:谁在操控这些推子?- 操控方式多样:可直接通过
C++或 蓝图 修改单个Control Bus;若需要根据场景(如Boss战)批量控制多个总线,则可使用Control Bus Mix(同样支持代码/蓝图激活)
控制总线(Control Bus)
Control Bus 并不复杂,它是一个包含参数的类,可用于调制音频信号的 音量、音高 等属性。
控制总线混音(Control Bus Mix)
正如前文所述,Control Bus Mix 是一组针对控制总线的操作集合,用于同时调控多个 Control Bus。但要注意:设置混音后仍可单独调整总线值,两种调控方式可以共存。
参数补丁(Parameter Patch)
不同混音之间的关系由 Parameter Patch 控制,这个类包含一组 Control Bus 和一组参数。例如:
- 用
PP_Music混合CB_Main(主音量)和CB_Music(音乐音量) - 用户既能在设置中调节主音量,也可单独调整音乐音量
- 可以将
CBM视为总控面板,调节时会按比例影响所有关联总线值 - 而单个
Control Bus则用于精确控制特定参数
音频设置(AudioSettings)
让我们回到源码分析。查看 AudioSettings 类会发现,它只有有意义的头文件,cpp 文件是空的。这个文件本身很简单,且大部分技巧已在先前文章中介绍过,简单回顾:
- 设置类需继承
UDeveloperSettings才能被引擎自动发现 - 类元标记
config = Game, default config, meta = (DisplayName = "LyraAudioSettings")表示:- 类内容将保存在 D
efaultGame.ini配置文件的LyraAudioSettings章节
- 类内容将保存在 D
- 属性元标记
config, EditAnywhere, Category = MixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBusMix")表示:- 属性会存入上述配置文件
- 可在原型或实例上编辑
- 归入
MixSetting分类 - 仅接受
AudioModulation插件的SoundControlBusMix类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// ...
USTRUCT()
struct LYRAGAME_API FLyraSubmixEffectChainMap
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, meta = (AllowedClasses = "/Script/Engine.SoundSubmix"))
TSoftObjectPtr<USoundSubmix> Submix = nullptr;
UPROPERTY(EditAnywhere, meta = (AllowedClasses = "/Script/Engine.SoundEffectSubmixPreset"))
TArray<TSoftObjectPtr<USoundEffectSubmixPreset>> SubmixEffectChain;
};
UCLASS(config = Game, defaultconfig, meta = (DisplayName = "LyraAudioSettings"))
class LYRAGAME_API ULyraAudioSettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
/** The Default Base Control Bus Mix */
UPROPERTY(config, EditAnywhere, Category = MixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBusMix"))
FSoftObjectPath DefaultControlBusMix;
/** The Loading Screen Control Bus Mix - Called during loading screens to cover background audio events */
UPROPERTY(config, EditAnywhere, Category = MixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBusMix"))
FSoftObjectPath LoadingScreenControlBusMix;
/** The Default Base Control Bus Mix */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBusMix"))
FSoftObjectPath UserSettingsControlBusMix;
/** Control Bus assigned to the Overall sound volume setting */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBus"))
FSoftObjectPath OverallVolumeControlBus;
/** Control Bus assigned to the Music sound volume setting */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBus"))
FSoftObjectPath MusicVolumeControlBus;
/** Control Bus assigned to the SoundFX sound volume setting */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBus"))
FSoftObjectPath SoundFXVolumeControlBus;
/** Control Bus assigned to the Dialogue sound volume setting */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBus"))
FSoftObjectPath DialogueVolumeControlBus;
/** Control Bus assigned to the VoiceChat sound volume setting */
UPROPERTY(config, EditAnywhere, Category = UserMixSettings, meta = (AllowedClasses = "/Script/AudioModulation.SoundControlBus"))
FSoftObjectPath VoiceChatVolumeControlBus;
/** Submix Processing Chains to achieve high dynamic range audio output */
UPROPERTY(config, EditAnywhere, Category = EffectSettings)
TArray<FLyraSubmixEffectChainMap> HDRAudioSubmixEffectChain;
/** Submix Processing Chains to achieve low dynamic range audio output */
UPROPERTY(config, EditAnywhere, Category = EffectSettings)
TArray<FLyraSubmixEffectChainMap> LDRAudioSubmixEffectChain;
// ...
};
可用设置项
Lyra当前支持的设置包括:
DefaultControlBusMixLoadingScreenControlBusMixUserSettingsControlBusMixOverallVolumeControlBusMusicVolumeControlBusSoundFXVolumeControlBusDialogueVolumeControlBusVoiceChatVolumeControlBus
应用场景
我们已经深入了解了音频系统,现在只剩下几个零散的子系统。
没有比系统原作者更好的解释方式了 Lyra Audio System
音乐管理组件(Music Manager Component)
Lyra Audio System 已详细解释了音乐系统。它位于名为 mx_system 的元声音类中,被封装成组件以便不同游戏功能(如触发战斗插曲)调用。
由于游戏功能在运行时向目标对象加载组件,将其设计为组件非常自然。该组件会被注入到 PlayerState,主要功能包括:
- 管理战斗强度:开火和战斗行为会增加强度值,随时间衰减
- 高强度时进入战斗模式,背景音乐减弱以突出枪声
- 低强度时进入探索模式,背景音乐增强并切换至环境音效
- 更新玩家视角方向(虽然游戏中未发现实际用途)
- 比赛结束时重置强度值为0(清理)
风声系统(Wind System)
Lyra Audio System 同样解释了风声系统,它更为复杂:
- 通过射线检测环境形状
- 系统会向四周发射射线判定环境轮廓,据此调制风声
- 根据玩家当前 速度(
velocity) 调整音量,奔跑时风声更大
子弹呼啸系统(Whizby System)
Lyra Audio System 也涵盖了这个系统。当子弹从玩家附近飞过时,系统会计算子弹轨迹与玩家的垂直向量,根据角度和距离播放声音(越近声音越大)。我们在 lib_Whizby 元声音资源中已见过其实现。
Whizby 是全局蓝图函数,在各武器的 OnBurst 开火事件中被调用。它会确定生成呼啸声的位置,最终通过 lib_Whizby 播放音效。
















