用 RealityKit 音频增强你的空间计算应用
Enhance your spatial computing app with RealityKit audio
2024年6月10日
一句话判断
RealityKit 的空间音频 API 让你用几行代码就实现六自由度的空间化音效,从引擎声的方向性到碰撞的物理反馈再到混响的空间感知,这是做沉浸式 visionOS 应用音频层最实用的指南。
这场 Session 讲了什么
这场 Session 以一个太空飞船游戏为例,逐步展示了 RealityKit 音频 API 的全部能力。飞船的声音设计采用了多层结构——每个引擎有蒸汽尾迹、排气管和涡轮三层声音,分别用文件播放和实时生成两种方式实现。
RealityKit 的音频播放天生就是空间化的。调用 Entity.playAudio 时,音频自动获得六自由度渲染——当你或音源移动、旋转时,音量和音色会实时更新。空间音频源具有基于物理的距离衰减,远处的声音自然地变弱。而且空间音频源会根据你真实的周围环境实时模拟混响效果。
今年新增了几个重要的音频能力。SpatialAudioComponent 让你可以设置音源的方向特性(directivity),控制声音的传播模式——从全方向到窄束。AudioPlaybackController 提供了对单个声音事件的精细控制——增益调节、淡入淡出、播放/暂停/停止。混响和门户穿越功能让声音随空间变化——飞船穿过 portal 进入太空时,混响特性会自动切换。Audio Mix Groups 则让你把不同类型的声音分组,让用户自定义各组的音量比例。
值得深挖的点
Directivity 模式把声音从”点”变成了”有方向的实体”
默认情况下,空间音频源向所有方向均匀传播声音。但现实中很多声源是有方向性的——引擎的排气声从后方更响,扬声器的声音从前方更清晰。SpatialAudioComponent 的 directivity 属性让你控制这个行为。
Beam directivity 模式在 0 到 1 之间工作:0 是全方向(所有方向音色一致),1 是窄束模式(声音集中在正面,背面更安静且更闷)。设置一个中间值(比如 0.25)可以得到自然的方向性——正面清晰、背面不消失但明显变暗。在飞船的例子中,把音源放在引擎排气口的位置,设置 beam 模式后,当飞船旋转使引擎朝向你时声音变得丰富,转开后声音变闷变远——这种效果让虚拟物体在听觉上变得”有体积”。
实现这个效果的关键是把音频源 Entity 作为引擎 Entity 的子节点,然后旋转音频源 Entity 使声音从排气口方向传出。这个小小的架构决策——音频源不直接挂在引擎上而是通过独立的子节点——给了你独立控制声音方向的灵活性。
实时生成音频打开了程序化声音设计的大门
文件播放适合循环的环境声和一次性的音效,但有些声音需要根据游戏状态实时变化。飞船的涡轮声就是一个好例子——音高和音量都需要随油门值连续变化,文件播放无法实现这种程度的动态性。
RealityKit 支持实时生成音频,让你用代码合成声音。结合 SpatialAudioComponent 的增益属性,你可以在自定义 System 中每帧更新声音参数——读取油门值,映射到分贝范围,更新增益。从线性油门值到对数分贝的映射是关键:人耳对音量的感知是对数的,直接用线性值会让低油门段听起来变化太快。
这种”音频参数由游戏逻辑驱动”的模式和图形渲染中的 shader 参数驱动是同一个思路——把音频当成和视觉一样可编程的动态元素,而不是预录的静态资源。
代码片段
空间音频的基础播放与淡入
import RealityKit
// 从 bundle 加载音频文件
let vaporTrailURL = Bundle.main.url(forResource: "vapor_trail", withExtension: "wav")!
let resource = try await AudioFileResource(
contentsOf: vaporTrailURL,
configuration: AudioFileResource.Configuration(
shouldLoop: true, // 循环播放
shouldRandomizeStartTime: true // 多个引擎从不同位置开始
)
)
// 从引擎实体播放——自动空间化
let controller = engineEntity.playAudio(resource)
// 淡入处理:从静音渐变到正常音量
controller.gain = -.infinity // -infinity 表示静音,0 为标称音量
controller.fade(to: 0, duration: 1.0) // 1 秒内淡入到正常音量
场景:飞船引擎的持续环境声。坑点:空间音频会将文件下混为单声道后再空间化,建议直接制作单声道文件以避免下混产生的伪影。
设置 Directivity 控制声音方向性
// 创建独立的音频源实体
let audioSource = Entity()
// 作为引擎的子节点——位置跟随引擎
engineEntity.addChild(audioSource)
// 配置空间音频组件
let spatialAudio = SpatialAudioComponent()
// beam 模式:0.25 提供适度的方向性
spatialAudio.directivity = .beam(focus: 0.25)
audioSource.components.set(spatialAudio)
// 从音频源(而非引擎本身)播放声音
audioSource.playAudio(resource)
// 旋转音频源使声音从排气口方向传出
audioSource.transform.rotation = simd_quatf(angle: .pi, axis: [0, 1, 0])
场景:让引擎声有方向性——从正面听清晰,从背面听发闷。坑点:别忘了旋转音频源 Entity,否则声音方向可能和视觉不一致。
动态增益控制——油门驱动的音量变化
// 自定义音频系统,每帧更新音量
class SpaceshipAudioSystem: System {
func update(context: SceneUpdateContext) {
// 查询所有有音频组件的引擎实体
let engines = context.entities(
matching: SpatialAudioComponent.self
)
for engine in engines {
guard let throttle = engine.components[ThrottleComponent.self],
let audio = engine.components[SpatialAudioComponent.self] else {
continue
}
// 线性油门值 -> 对数分贝
// throttle 0 = 静音, throttle 1 = 最大音量
let decibels: Float
if throttle.value < 0.01 {
decibels = -.infinity // 完全静音
} else {
// 对数映射:低音量段变化更平缓
decibels = 20 * log10(throttle.value)
}
var updatedAudio = audio
updatedAudio.gain = decibels
engine.components.set(updatedAudio)
}
}
}
场景:排气声随油门值实时变化音量。坑点:直接用线性值控制增益会导致低油门段听起来变化过快——必须用对数映射。
最佳实践
- 音频文件使用单声道:空间化前会强制下混为单声道,提前用单声道文件避免下混伪影。
- 短文件循环 + 随机起点 = 丰富的声音:一个短的循环文件配合
shouldRandomizeStartTime,可以让多个相同音源听起来各不相同。 - 从零音量淡入避免爆音:循环文件通常起始很突然,用
fade(to:duration:)做淡入可以让声音自然地出现。 - 音频源独立于视觉实体:创建专门的 Entity 作为音频源并设为子节点,这样你就能独立控制声音的方向和位置。
- 用 Mix Groups 给用户控制权:把音效、音乐、语音分成不同的 Mix Group,让用户在设置中独立调整各组音量。
还有什么值得关注
- 碰撞音频可以为虚拟物体间以及虚拟与真实物体间的碰撞自动生成声音,增强物理交互的沉浸感。
- 混响特性会跟随 app 进入不同的空间(比如通过 portal 从房间进入太空),自动切换混响模式。
- Session 提到的
SpatialTrackingService、物理力效果、物理关节和 portal 穿越组件都在《Discover RealityKit APIs for iOS, macOS, and visionOS》中有详细讲解。