目录

Wwise SDK 2019.2.9
集成 Listener

简介

听者是代表游戏中麦克风位置的游戏对象。将一个游戏对象指定为听者可以让 3D 声音被指派到扬声器,以便模拟真实的 3D 环境。与之相似,发声体游戏对象代表一个虚拟扬声器,当它被指派到一个听者时,发声体的位置信息就被映射到听者的坐标系统中,以此渲染出 3D 声音。Wwise 中会给所有的游戏对象指派一个变换(transform)——一个位置向量以及前方和上方向量,不管这些对象是作为发声体还是听者(或两者都是)。游戏对象的变换必须每帧更新,保证声音通过正确的扬声器进行渲染。

注册听者

为了听到声音,至少要注册一个游戏对象并且将其指派为听者。您可以使用 AK::SoundEngine::SetDefaultListeners 来将听者指派到所有其它游戏对象,或者用 AK::SoundEngine::SetListeners 来将听者指派到特定游戏对象,并覆盖使用 AK::SoundEngine::SetDefaultListeners 所做的设置。以下是我们注册游戏对象并将其指派为默认听者的方法:

AkGameObjectID MY_DEFAULT_LISTENER = 0;
// 注册主要听者。
AK::SoundEngine::RegisterGameObj(MY_DEFAULT_LISTENER, "My Default Listener");
// 将一个听者设置为默认。
AK::SoundEngine::SetDefaultListeners(&MY_DEFAULT_LISTENER, 1);
// 注册游戏对象来播放声音
AkGameObjectID MY_EMITTER = 1;
AK::SoundEngine::RegisterGameObj(MY_EMITTER, "My Emitter");
// 这时“My Emitter”有一个听者,即“My Default Listener”,因为我们已将其指派为默认听者。
AkGameObjectID MY_LISTENER_NO2 = 2;
AK::SoundEngine::RegisterGameObj(MY_LISTENER_NO2, "My Listener #2");
// 如果我们只想为“My Emitter”变更听者,可以按以下方式:
AK::SoundEngine::SetListeners(MY_EMITTER , &MY_LISTENER_NO2, 1);
// 这时,“My Emitter”有一个听者叫作“My Listener #2”。所有其它游戏对象仍然有“My Default Listener”作为它们的听者。

您可以通过在 Wwise 设计工具中查看 Advanced Profiler 的 Emitter-Listener 选项卡,来检查已在代码中指派的发声体-听者关联。简单的游戏会选择一个游戏对象作为所有游戏对象的默认听者;但是也可以使用多个听者输出到一个输出设备上。请见以下的 单一输出设备中的多个听者 。我们也可以将听者用于子混音的 3D 定位。想要做到这点,需要将听者指派到特定的游戏对象,这些游戏对象同时也是听者,它们创建出游戏对象和听者相互关联形成的有向图。

设置 Listener 的位置信息

就像针对所有游戏对象一样,要使用 AK::SoundEngine::SetPosition() 函数来设置听者的位置。只要 Listener 的任何位置或朝向向量发生变化,就应执行此操作。

AkTransform listenerTransform;
(... 在此设置 listenerTransform 的位置和朝向成员……)
AK::SoundEngine::SetPosition( listenerPosition );

AkTransform 类型保存着在游戏 3D 空间中定义听者位置和指向的信息。听者的位置(Position),OrientationFront,以及 OrientationTop 向量可以使用 AkTransform 类型的 getter 和 setter 来访问。

备注: OrientationFront 向量定义 Listener 头部的朝向。此向量应该与 OrientationTop 向量正交,后者用于定义 Listener 头的斜度。对于人类听者,可以将 OrientationFront 向量视为听者的鼻子(伸出面部指向远处),而 OrientationTop 向量与它正交,从鼻子指向上方,位于听者眼睛之间,经过额头继续向上。

请参阅 X-Y-Z 坐标系统 了解 Wwise 声音引擎中 X、Y 和 Z 轴分别对应什么。

必须定义朝向向量,才能正确渲染音频。它们不可以是零向量,需要是单位向量。它们也必须是直角。

备注: Listener 的信息最多每帧更新一次。即使多次调用了 AK::SoundEngine::SetPosition() 函数,调用 AK::SoundEngine::RenderAudio() 时只考虑最后一个值。
技巧: 如果您遇到声音渲染上的异常情况,例如原希望从左扬声器中发出的声音结果却从右扬声器中发出,则应通过 AK::SoundEngine::SetPosition() 函数检查提供给声音引擎的 Listener 位置信息。您可以尝试设置一个已知的 Listener 恒定位置,检查在此情况下渲染是否正确,以排除混淆了 X、Y 和 Z 轴的情况。有关此功能的详情,请参阅 X-Y-Z 坐标系统

单一输出设备中的多个听者

在单人游戏中,您始终只有一个视角,因此一个 Listener 就够了。然而,如果多个玩家可以同时在同一系统上玩游戏,或者同时显示多个视角,那么各个视角都需要有自己的 Listener ,从而正确地为所有这些视角渲染音频。

实现多个 Listener 的主要难点在于声源的定位对于玩家所见并不总是合理。这主要是游戏仅使用一组扬声器来为多个玩家再现 3D 环境所造成的。

下图简要地展示了这一问题。由于 Listener 0 希望在左扬声器中听到声源,而 Listener 1 希望在右扬声器中听到声源,因此很难讲哪些扬声器应该用来播放声源。

图:两个 Listener 在不同的扬声器中听到相同的声源

Wwise 可设置任意数量的听者。除非以下情况,将默认在主输出设备中合并所有听者:

以下的部分包括了当所有听者融合到同一个输出设备时的例子,也描述了 Wwise 声音引擎如何让程序员操作这些听者,以便完成期望的行为。

备注: 与多个 Listener 相关的一切功能都只能由游戏程序员通过 SDK 来实现。Wwise 设计工具中没有专门的选项用来为多个 Listener 管理声源在游戏中的定位。

多个 Listener :捕获声源

每个听者都会生成一个混音图。对于各个声源,相对于它们所作用的各个 Listener 分别计算距离衰减和声锥衰减。

音量衰减管理

当多个听者在捕获一个声源时,声源会在它相应听者对应的每条总线实例中连续混音。在混音时,每个听者会独立应用衰减音量。

LPF 衰减管理

与衰减音量相反,衰减 LPF 和 HPF 会直接在声源上应用;因此,Wwise 必须基于特定声源的所有发声体-听者关联选择一个值。以下是声音引擎如何计算要应用于各个声源的最终低通滤波器:

  1. 此声源所作用的各个 Listener :
    1. 根据声源与此 Listener 之间的距离计算 LPF。
    2. 根据声源与此 Listener 之间的角度计算 LPF。
    3. 保留两个值中的最大值。
  2. 获得所有 Listener 中的最小值,并添加对象的 LPF(常规值和 RTPC)。

在下表中详述的示例中, Listener 0 的值是 max( 10, 40 ) = 40, Listener 1 的值是 max( 50, 10 ) = 50。两值中的最小数是 40,然后将此数与对象的值 5 相加,从而获得最终值 45:

Listener 0
Listener 1
Object
Final LPF for
the source
锥形 LPF
半径 LPF
锥形 LPF
半径 LPF
10 40 50 10 5 45

音量偏置与空间化

3D Spatialization(3D 空间化)会根据声音相对于听者的位置在各个扬声器之间对声音进行声像摆位。

然而,如果两个玩家在分屏显示器上玩游戏,您可能会希望在左扬声器中听到听者 1(第一个玩家),在右扬声器中听到听者 2(第二个玩家),而完全不用根据声音相对于各个 Listener 的位置在扬声器之间定位声音的常规办法。

为了提供更多的控制和更高的灵活性,Wwise 可以让游戏程序员对指定的 Listener 禁用空间定位,也可为各个声道设置自定义音量偏置,从而指定在各个扬声器中将如何听到此 Listener 捕获的声音。

可以调用 AK::SoundEngine::SetListenerSpatialization() 来修改各个 Listener 的这些设置:

AkGameObjectID in_uListenerID, // Listener game object ID
bool in_bSpatialized, // Spatialization toggle (True : enable spatialization, False : disable spatialization)
AkChannelConfig in_channelConfig, // Channel configuration associated with volumes in_pVolumeOffsets. 如果 in_pVolumeOffsets 为空,则忽略。
AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets = NULL // 各个扬声器的音量偏置,单位:dB。请参见 AkSpeakerVolumes.h 了解如何操作此向量。
) = 0;

第一个参数是 Listener ID。第二个参数必须设置为 True ,以针对此 Listener 启用空间定位,或者设置为 False ,以禁用它。最后两个参数代表包含该 Listener 上各个声道的衰减的向量,单位:dB。如果 in_bSpatializedFalse ,则它为各个声道设置音量,在默认情况下音量是 0dB。若 in_bSpatializedTrue,则针对各个声道按给定量偏置默认 3D 空间化计算得出的音量。

音量向量绑定到声道配置 in_channelConfig 。如果 in_channelConfig 为 5.1,则音量向量应有 6 个值。使用 AK::SpeakerVolumes::Vector namespace 中定义的功能操作它。声道顺序对应于 AkSpeakerConfig.h 中定义的声道掩码位(channel mask bits),LFE 除外(总是位于末尾的)。

对于两个玩家使用分屏玩游戏的例子,程序员可使用以下代码:

// 将听者 1 和 2 注册为所有发声体的默认听者。
// 您也可能最好通过换用 AK::SoundEngine::SetListeners 来明确选择哪些发声体向哪些听者发声。
AkGameObjectID listeners[2] = {1,2};
AK::SetDefaultListeners(listeners[0],2);
// 使用 7.1 扬声器设置定义扬声器偏置(如果平台支持的话)
vVolumes[0] = 0.f; // 左
vVolumes[1] = -96.3f; // 右
vVolumes[2] = -6.f; // 中置
vVolumes[3] = 0.f; // 左后
vVolumes[4] = -96.3f; // 右后
vVolumes[5] = 0.f; // 侧左
vVolumes[6] = -96.3f; // 侧右
vVolumes[7] = 0.f; // LFE
AK::SoundEngine::SetListenerSpatialization( listeners[0], false, cfg, vVolumes );
vVolumes[0] = -96.3f; //左
vVolumes[1] = 0.f; // 右
vVolumes[2] = -6.f; // 中置
vVolumes[3] = -96.3f; // Rear left
vVolumes[4] = 0.f; // 右后
vVolumes[5] = -96.3f; // Side left
vVolumes[6] = 0.f; // 侧右
vVolumes[7] = 0.f; // LFE
AK::SoundEngine::SetListenerSpatialization( listeners[1], false, cfg, vVolumes );

如果声音通往的总线按照用户定义的声道配置而不同于 7.1,则声道向量将使用标准的下混配方在内部下混,然后再作用于声音。

要改回使用常规的空间化,您可以调用:

// Enable regular spatialization on listeners 0 and 0

音量管线

下图按照执行的顺序展示对各个声源所做的不同操作,以使各个 Listener 能够计算各个扬声器中的最终音量:

图:Wwise 声音引擎中的音量管线
参见
AKSOUNDENGINE_API AKRESULT SetDefaultListeners(const AkGameObjectID *in_pListenerObjs, AkUInt32 in_uNumListeners)
AkUInt32 uNumChannels
Number of channels.
Definition: AkSpeakerConfig.h:510
AkUInt64 AkGameObjectID
Game object ID
Definition: AkTypes.h:65
AKRESULT
Standard function call result.
Definition: AkTypes.h:122
AKSOUNDENGINE_API AKRESULT RegisterGameObj(AkGameObjectID in_gameObjectID)
AkForceInline AkUInt32 GetRequiredSize(AkUInt32 in_uNumChannelsIn, AkUInt32 in_uNumChannelsOut)
Compute size (in bytes) required for given channel configurations.
#define NULL
Definition: AkTypes.h:49
AkReal32 * VectorPtr
Volume vector. Access each element with the standard bracket [] operator.
Definition: AkSpeakerVolumes.h:49
#define AkAlloca(_size_)
Stack allocations.
Definition: AkPlatformFuncs.h:117
AKSOUNDENGINE_API AKRESULT SetListenerSpatialization(AkGameObjectID in_uListenerID, bool in_bSpatialized, AkChannelConfig in_channelConfig, AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets=NULL)
#define AK_SPEAKER_SETUP_7_1
Definition: AkSpeakerConfig.h:200
AKSOUNDENGINE_API AKRESULT SetListeners(AkGameObjectID in_emitterGameObj, const AkGameObjectID *in_pListenerGameObjs, AkUInt32 in_uNumListeners)
AkForceInline void SetStandard(AkUInt32 in_uChannelMask)
Set channel config as a standard configuration specified with given channel mask.
Definition: AkSpeakerConfig.h:544
uint32_t AkUInt32
Unsigned 32-bit integer
Definition: AkTypes.h:86
AKSOUNDENGINE_API AKRESULT SetPosition(AkGameObjectID in_GameObjectID, const AkSoundPosition &in_Position)
Position and orientation of game objects.
Definition: AkTypes.h:323