목차

Wwise SDK 2019.2.9
리스너 통합하기

소개

리스너는 게임에서 마이크 위치를 나타내는 게임 오브젝트입니다. 리스너를 게임 오브젝트로 지정하면 스피커에 할당된 3D 사운드를 통해 실제 3D 환경과 같이 구현할 수 있습니다. 마찬가지로, 방사체 게임 오브젝트는 가상 스피커를 나타내며, 이 방사체 게임 오브젝트가 리스너에 할당되면 방사체의 위치 정보가 리스너의 좌표 시스템에 매핑되어 3D 사운드를 렌더링합니다. Wwise의 게임 오브젝트는, 방사체로 동작하든 리스너로 동작하든 (혹은 두 경우 모두에 해당하든), 트랜스폼 즉, 위치 벡터나 앞쪽과 위쪽 방향 벡터가 할당됩니다. 게임 오브젝트의 트랜스폼은 반드시 각 프레임마다 업데이트돼야 사운드가 올바른 스피커를 통해 렌더링됩니다.

리스너 등록하기

소리가 들리도록 하기 위해서는 최소 한 개의 게임 오브젝트가 등록되어 리스너로 할당돼있어야 합니다. 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 위치 지정에도 리스너를 사용할 수 있습니다. 그러기 위해서는 게임 오브젝트에 리스너를 할당해줘야 합니다. 여기서 이 게임 오브젝트는 방사체-리스너 관계로 연결된 게임 오브젝트 그래프를 생성하는 리스너이기도 합니다.

리스너의 위치 정보 설정하기

리스너 위치를 설정할 때에는, 모든 게임 오브젝트와 동일하게 AK::SoundEngine::SetPosition() 함수를 사용합니다. 리스너 위치나 방향 벡터가 변할 때마다 이와 같이 처리돼야 합니다.

AkTransform listenerTransform;
(... 여기에 listenerTransform의 위치와 방향 멤버를 설정합니다...)
AK::SoundEngine::SetPosition( listenerPosition );

AkTransform 클래스에는 게임의 3D 공간에서 리스너의 위치와 방향 정보가 있습니다. 리스너의 위치(Position)인 OrientationFront 및 OrientationTop 벡터는 AkTransform 클래스의 getter와 setter를 이용해 접근할 수 있습니다.

참고: OrientationFront 벡터는 리스너의 머리가 향하는 방향을 정의합니다. OrientationFront 벡터는 OrientationTop 벡터와 직각이 돼야 하며, 리스너 머리의 기울기를 정의합니다. 리스너가 사람인 경우 OrientationFront 벡터는 리스너의 코를 나타내며 (얼굴에서 바깥쪽을 향해 뻗음), OrientationTop 벡터는 리스너의 두 눈 사이 코에서부터 시작해 이마를 지나 수직 방향으로 뻗어 올라가는 값을 나타냅니다.

Wwise 사운드 엔진에서 X, Y, Z를 정의하는 방법에 관한 정보는 X-Y-Z 좌표 시스템 을(를) 참고하세요.

오디오가 올바르게 렌더링되기 위해서는 방향 벡터가 반드시 정의돼있어야 합니다. 이 방향 벡터는 영벡터가 될 수 없으며 반드시 단위 벡터여야합니다. 또한 올바른 각도로 정의돼야 합니다.

참고: 리스너의 위치는 최대 프레임당 한 번 업데이트됩니다. AK::SoundEngine::SetPosition() 함수를 여러번 호출하도록 설정해놔도 AK::SoundEngine::RenderAudio() 가 호출될 때 마지막 값만 처리됩니다.
작은 정보: 예를 들어 왼쪽 스피커에서 나와야 할 소리가 오른쪽 스피커에서 나오는 것과 같이 잘못된 사운드 렌더링이 발생한 경우, 사운드 엔진에 AK::SoundEngine::SetPosition() 함수로 제공된 리스너의 위치 정보를 확인해보세요. 알고 있는 상수 리스너의 위치를 설정하고 렌더링이 올바른지 확인하여, 이런 경우 X, Y, Z 축에서 올바르지 않은 값을 제외시키세요. 이와 관련한 더 자세한 정보는 X-Y-Z 좌표 시스템 을(를) 참고하세요.

단일 출력 장치에서 여러 리스너가 있는 경우

게임 화면이 언제나 하나의 시점만 보여주는 싱글 플레이어 게임에는 리스너 한 개면 충분합니다. 그러나 여러 플레이어가 한 시스템에서 게임을 플레이할 경우나 동시에 여러 시점을 보여주는 경우, 각 시점에는 각각의 리스너가 따로 있어야 그에 맞는 오디오를 적절히 렌더링할 수 있습니다.

여러 리스너를 구현할 때 가장 어려운 점은, 음원의 위치가 플레이어가 보는 내용과 항상 일치하지는 않는다는 점입니다. 여러 플레이어에 대해 3D 환경을 재현하는데 스피커 세트를 하나만 사용하는 게임에서 주로 이런 어려움이 발생합니다.

다음 그림은 이런 문제를 간략히 나타내고 있습니다. Listener 0에게는 왼쪽 스피커에서 음원이 들려야하는 반면 Listener 1에게는 오른쪽에서 들려야하기 때문에 이 음원이 어느 스피커에서 재생돼야 하는지 결정하기가 매우 어렵습니다.

그림: 동일한 음원을 서로 다른 스피커를 통해 듣는 두 리스너

Wwise는 리스너 숫자에 제한이 없으며, 기본 설정으로 모든 리스너가 주요 출력 장치에서 혼합됩니다. 단, 다음의 경우는 예외가 됩니다.

다음 섹션은 모든 리스너가 동일한 출력 장치로 병합되는 경우와, 프로그래머가 Wwise 사운드 엔진을 이용해 이 리스너들로 원하는 동작을 만들어내는 과정을 다루고 있습니다.

참고: 복수의 리스너와 관련된 모든 작업은 게임 프로그래머가 SDK를 이용해 구현해야 합니다. Wwise 저작 애플리케이션에는 복수의 리스너에 대해 게임 내 음원의 위치를 관리하는 특별한 옵션이 없습니다.

복수의 리스너: 음원 캡처하기

각 리스너는 믹싱 그래프를 스폰합니다. 각 음원과 거리, 원뿔 감쇠(cone attenuation)는 각 활성화된 리스너에 따라 개별적으로 계산됩니다.

볼륨 감쇠 관리

여러 리스너가 음원을 캡처할 때, 그 음원은 각각의 리스너에 해당하는 각 버스 인스턴스에 연속적으로 믹싱됩니다. 믹싱되면 해당 감쇠 볼륨이 각 리스너에 개별적으로 적용됩니다.

LPF 감쇠 관리

감쇠 볼륨과는 반대로, 감쇠 LPF와 HPF는 음원에 직접 적용됩니다. 따라서 Wwise는 주어진 음원의 모든 방사체-리스너 관계를 기반으로 하나의 값을 선택해야 합니다. 다음은 사운드 엔진이 최종 Low Pass Filer(저역 통과 필터)를 각 음원에 계산해 넣는 과정입니다.

  1. 해당 음원이 활성화돼있는 각 리스너에 대해
    1. 음원과 리스너 사이 거리를 기반으로 LPF를 계산합니다.
    2. 음원과 리스너 사이 각도를 기반으로 LPF를 계산합니다.
    3. 두 값 중 더 큰 값을 남겨둡니다.
  2. 모든 리스너들 중 가장 낮은 값을 취해 오브젝트의 LPF를 추가합니다 (일반값 및 RTPC).

다음 표에 나온 자세한 예시를 보면, 리스너 0의 값은 max( 10, 40 ) = 40이고, 리스너 1의 값은 max( 50, 10 ) = 50입니다. 두 값 중 가장 낮은 값은 40이므로 여기에 오브젝트 값 5를 더하면 최종 값 45가 나옵니다.

리스너 0
리스너 1
오브젝트
음원에 대한
최종 LPF
원뿔 LPF
반경 LPF
원뿔 LPF
반경 LPF
10 40 50 10 5 45

볼륨 상쇄와 공간화

3D 공간화(Spatialization)는 리스너와 관련된 사운드의 위치 정보를 기반으로 다양한 스피커에 소리를 패닝합니다.

그러나 두 플레이어가 게임 화면을 둘로 나눠 플레이하는 경우, 왼쪽 스피커에서 리스너 1(첫 번째 플레이어)의 소리를 내고 오른쪽 스피커에서는 리스너 2(두 번째 플레이어)의 소리를 내야 할 수 있습니다. 즉, 각 리스너의 위치에 따라 스피커에 대한 사운드의 일반적인 위치 지정을 사용하지 않을 수 있습니다.

Wwise는 제어의 폭을 넓히기 위해, 게임 프로그래머가 주어진 리스너에 대한 공간화(spatialization)를 비활성화할 수 있게 했으며, 필요한 경우 각 채널에 사용자 지정 볼륨 상쇄를 설정할 수 있어 해당 리스너가 캡처하는 사운드가 각 스피커에서 어떻게 들릴 것인지를 지정할 수 있습니다.

이러한 설정은 AK::SoundEngine::SetListenerSpatialization() 를 호출해 각각의 리스너에 대해 변경할 수 있습니다.

AkGameObjectID in_uListenerID, // 리스너 게임 오브젝트 ID
bool in_bSpatialized, // 공간화 토글 (True : 공간화 활성화, False : 공간화 비활성화)
AkChannelConfig in_channelConfig, // in_pVolumeOffsets의 볼륨과 연관된 채널 환경 설정. in_pVolumeOffsets이 NULL이면 무시함.
AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets = NULL // 스피커당 볼륨 상쇄 (단위: dB). 이 벡터를 다루는 방법은 AkSpeakerVolumes.h 를 확인하세요.
) = 0;

첫 번째 매개 변수는 리스너 ID입니다. 두 번째 매개 변수는 True 로 설정해야 해당 리스너에 대한 공간화를 활성화하며, 반대로 False 는 비활성화합니다. 끝으로, 마지막 두 매개 변수는 해당 리스너의 각 채널에 대한 감쇠를 갖고 있는 벡터를 dB로 나타냅니다. in_bSpatializedFalse 이면 각 채널의 볼륨을 기본 0 dB로 설정합니다. in_bSpatializedTrue 이면 기본 3D 공간화 계산으로 각 채널에 주어진 값에 의해 계산된 볼륨을 상쇄합니다.

볼륨 벡터는 채널 환경 설정 in_channelConfig 에 따라 좌우됩니다. 만약 in_channelConfig 가 5.1이면 볼륨 벡터는 6 개의 값을 갖습니다. AK::SpeakerVolumes::Vector 네임스페이스에 정의된 함수를 이용해 볼륨 벡터를 설정하세요. 채널 순서는 AkSpeakerConfig.h 에 정의된 채널 마스크 비트 순서대로입니다. 단, 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; // 왼쪽 뒤
vVolumes[4] = 0.f; // 오른쪽 뒤
vVolumes[5] = -96.3f; // 왼쪽 측면
vVolumes[6] = 0.f; // 오른쪽 측면
vVolumes[7] = 0.f; // LFE
AK::SoundEngine::SetListenerSpatialization( listeners[1], false, cfg, vVolumes );

사운드가 라우팅된 버스가 7.1이 아닌 다른 채널 환경 설정으로 돼있는 경우, 사용자가 정의한 채널 환경 설정에 따라 벡터가 사운드에 적용되기 전에 기본 다운믹스 방식을 이용해 내부적으로 다운믹스됩니다.

일반 공간화로 돌아가려면 다음을 호출하세요.

// 리스너 0와 1에 대해 일반 공간화를 활성화합니다

볼륨 작업 과정

아래 그림은 각 스피커의 최종 볼륨을 계산하기 위해 각 리스너에 대한 모든 음원에 적용되는 다양한 작업을 순서대로 보여줍니다.

그림: 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