더욱 효율적으로 음악 구현하기
Wwise를 사용해 게임 음악을 구현할 때 음악 루프가 자연스럽게 이어지는지 확인하기 위해서는 곡 전체를 재생해야 합니다. 예를 들어 2분 길이의 음악 구간이 있다면 루프 지점이 원활하게 이어지는지 확인하기 위해 전체 2분을 모두 들어봐야 하죠.
상호작용 음악에서 Segment 전환이나 Transition을 확인할 때도 마찬가지입니다. 특히 긴 트랙의 후반부에서 발생하는 전환의 경우, 문제가 없다면 한 번의 확인으로 충분할 수 있습니다. 하지만 문제가 있을 경우 설정이나 애셋을 수정한 뒤 긴 구간을 다시 재생해서 확인해야 합니다.
이러한 반복적인 확인 작업은 시간이 많이 들고 정신적으로도 좀 힘들죠. 시간이 부족할 경우 품질 저하로 이어질 수 있습니다.
이러한 문제를 해결하기 위해 플래티넘게임즈는 Wwise의 오프라인 렌더링 API를 활용해 오디오 루프와 전환을 보다 효율적으로 확인할 수 있는 도구인 'Music Render'를 독자적으로 개발했습니다. 이 글에서는 이 도구의 사용 예시와 기술적 구성을 소개합니다.
Music Render: 사용 예시
아래는 Wwise 예제 프로젝트 'Cube'를 기반으로 Music Render가 Wwise를 활용한 음악 구현에서 흔히 발생하는 문제들을 어떻게 효과적으로 해결하는지 보여주는 실용적인 사례들입니다.
예시 1: 단순 루프
Story Playlist Container는 약 1분 길이의 단일 Music Segment로 구성된 단순 루프입니다. 루프를 확인하려면 1분 동안 재생해야 하죠.
바로 여기서 Music Render가 빛을 발합니다. Wwise Playlist 메뉴에서 이 도구를 활성화 한뒤 다음의 매개 변수를 설정합니다.
- 렌더링 시간: 루프 확인이 필요한 길이 (1~10분)
- WAV 파일을 열 응용 프로그램: 예를 들어 MAGIX의 SOUND FORGE를 지정.
[렌더링 시작]을 클릭하면 SoundBank 생성과 렌더링이 자동으로 수행되며, 완료 후 WAV파일이 SOUND FORGE에서 열립니다. 루프 지점부터 음악 세그먼트를 바로 재생하여 루프를 빠르게 확인할 수 있습니다.
예시 2: 복잡한 Playlist
Explore Playlist Container에는 여러 개의 Music Segment가 포함되어 있습니다. 이 도구가 없다면 각 Music Segment의 전환에 문제가 없는지 확인하기 위해 전체를 오랜 시간 동안 재생해야 합니다.
렌더링 중에 Music Render는 생성된 파형 파일의 세그먼트 전환 지점에 마커를 자동으로 추가합니다. 이렇게 하면 전체 트랙을 처음부터 끝까지 들을 필요 없이 마커 위치에서 재생을 시작해 전환 지점을 바로 확인할 수 있습니다.
예시 3: 상호작용 전환
Wwise 201의 Music Switch Container는 Gameplay_Switch 스위치에 따라 음악이 Combat 또는 Explore로 전환됩니다.
전환을 확인하려면 보통 트랙을 수동으로 전환해야 하지만 Music Render를 사용하면 이러한 전환이 자동화된 이벤트를 생성하고 렌더링함으로써 보다 효율적으로 확인할 수 있습니다.
다음과 같은 구성으로 예제 이벤트를 생성합니다:
- Switch Container 재생
- 15초마다 스위치 값 변경
Music Render를 통해 해당 이벤트를 오프라인으로 렌더링하면, 트랙이 15초 간격(15초, 30초, 45초, ...)으로 전환되는 음악 파형이 생성됩니다. 부자연스러운 전환이 있을 경우, 트랜지션이나 애셋을 수정한 다음 다시 렌더링하여 수정 사항을 확인할 수 있습니다. 이러한 자동화를 통해 수동으로는 재현하기 어려운 문제도 확실하게 검증하고 수정할 수 있습니다.
기술 구성
Music Render는 프론트엔드와 백엔드로 구성되어 있습니다.
프론트엔드 (UI)
- 프로그래밍 언어: C#
- UI 프레임워크: WPF
- 실행 방식: Wwise 저작 툴에 Add-on 기능(.exe)으로 실행
- 기능:
- WAAPI를 통해 Wwise 저작 도구와 통신
- Event와 SoundBank 생성
- 백엔드에 렌더링 명령을 전송하고 렌더링이 완료된 후 WAV 파일을 열도록 설정
백엔드 (렌더링 처리)
- 프로그래밍 언어: C++
- 실행 방식: 프론트엔드에서 실행 (.dll).
- 기능:
- Wwise SDK로 사운드 엔진 초기화
- SoundBank 로드, Event 게시, 오프라인 렌더링을 통한 오디오 출력
- 처리 완료 후 결과를 프론트엔드로 반환
백엔드 세부 정보
Wwise 사운드 엔진 초기화나 뱅크 로드와 같은 기본적인 처리에 대해서는 공식 문서를 참고하세요. 여기에서는 오프라인 렌더링에 관련된 주요 사항들을 정리합니다.
설정이 필요한 전역 변수:
AK::OfflineRendering::g_fFrameTimeInSeconds = 1.0f / 60.0f;
AK::OfflineRendering::g_bOfflineRenderingEnabled = true;
이 변수들은 AkProfile.cpp에 정의되어 있습니다. Wwise 소스 코드에 접근하려면 일반적으로 적절한 라이선스가 필요하지만, 오프라인 렌더링 도구를 직접 제작할 때 꼭 필요한 것은 아닙니다.
오프라인 렌더링 API:
AK::SoundEngine::StartOutputCapture(settings.WavFileName);
AK::SoundEngine::StopOutputCapture();
마커 추가를 위한 콜백:
static void MusicCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
AK::SoundEngine::AddOutputCaptureMarker("Entry");
}
전체 코드:
위의 내용을 포함한 코드의 주요 부분은 다음과 같습니다. MusicRender 클래스와 이 클래스에서 사용하는 구조체 정의가 포함되어 있습니다. 오프라인 렌더링에 필요한 모든 기술적인 요소가 포함되어 있으며, 오류 처리 부분은 생략되어 있습니다.
// MusicRender.h
struct MusicRenderSettings{
wchar_t* SoundBankFolderName;
wchar_t* StreamFolderName;
wchar_t* SoundBankFileName;
wchar_t* PluginDllFolderName;
wchar_t* Event;
int RenderingDurationSec;
wchar_t* WavFileName;
};
typedef void(__stdcall* NotifyProgress)(float);
extern "C" __declspec(dllexport) void __stdcall MusicRender(const MusicRenderSettings&, const NotifyProgress);
// MusicRender.cpp
namespace AK {
namespace OfflineRendering
{
extern AkReal32 g_fFrameTimeInSeconds;
extern bool g_bOfflineRenderingEnabled;
}
}
CAkFilePackageLowLevelIODeferred g_lowLevelIO;
class CMusicRender
{
public:
const AkGameObjectID LISTENER_ID = 0;
const AkGameObjectID GAME_OBJECT_MUSIC = 1;
AkPlayingID m_iPlayingID = 0;
void Initialize(const MusicRenderSettings& settings)
{
AK::SoundEngine::RegisterGameObj(LISTENER_ID, "Listener (Default)");
AK::SoundEngine::SetDefaultListeners(&LISTENER_ID, 1);
g_lowLevelIO.SetBasePath(settings.SoundBankFolderName);
AK::StreamMgr::SetCurrentLanguage(AKTEXT("English(US)"));
AkBankID bankID;
AK::SoundEngine::LoadBank("Init.bnk", bankID);
AK::SoundEngine::LoadBank(settings.SoundBankFileName, bankID);
AK::SoundEngine::RegisterGameObj(GAME_OBJECT_MUSIC, "Music");
g_lowLevelIO.SetBasePath(settings.StreamFolderName);
}
void Terminate(const MusicRenderSettings& settings)
{
AK::SoundEngine::StopPlayingID(m_iPlayingID);
AK::SoundEngine::UnregisterGameObj(GAME_OBJECT_MUSIC);
AK::SoundEngine::UnloadBank(settings.SoundBankFileName, NULL);
AK::SoundEngine::UnloadBank("Init.bnk", NULL);
}
void Main(const MusicRenderSettings& settings, const NotifyProgress callback)
{
AK::OfflineRendering::g_fFrameTimeInSeconds = 1.0f / 60.0f;
AK::OfflineRendering::g_bOfflineRenderingEnabled = true;
AK::SoundEngine::StartOutputCapture(settings.WavFileName);
{
m_iPlayingID = AK::SoundEngine::PostEvent(settings.Event, GAME_OBJECT_MUSIC, AK_MusicSyncEntry, MusicCallback);
const int callbackIntervalMS = 10;
DWORD lastNotificationTimeMS = GetTickCount64();
float renderTimeSec = settings.RenderingDurationSec;
float audioBufferSec = AK::OfflineRendering::g_fFrameTimeInSeconds;
for (float timeSec = 0; timeSec < renderTimeSec; timeSec += audioBufferSec) {
AK::SoundEngine::RenderAudio();
if (GetTickCount64() - lastNotificationTimeMS >= callbackIntervalMS) {
callback(timeSec / renderTimeSec);
lastNotificationTimeMS = GetTickCount64();
}
}
}
AK::OfflineRendering::g_bOfflineRenderingEnabled = false;
AK::SoundEngine::StopOutputCapture();
}
static void MusicCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
AK::SoundEngine::AddOutputCaptureMarker("Entry");
}
};
void __stdcall MusicRender(const MusicRenderSettings& settings, const NotifyProgress callback)
{
if (InitSoundEngine(settings) == false) {
return;
}
CMusicRender cMusicRender;
cMusicRender.Initialize(settings);
cMusicRender.Main(settings, callback);
cMusicRender.Terminate(settings);
TermSoundEngine();
}
결론
이 도구는 2019년부터 플래티넘게임즈의 여러 타이틀에서 활용되고 있습니다. 음악 루프와 상호작용 전환을 효율적으로 확인하는 데 도움을 주며, 작업 시간을 절약하고 더 정교한 사운드 연출을 위한 기반이 됩니다. 이 도구를 사용하면 확인 작업을 빠르게 마칠 수 있어 게임 내 음악 표현을 다듬거나 실험하는 데 더 많은 시간을 쓸 수 있습니다.
또한 부가적인 이점으로, 개발 중 사운드 자료를 제공해 달라는 요청을 받았을 때 세그먼트 단위로 나뉘어 있는 여러 WAV 파일을 결합해 루프용 단일 WAV 파일로 간편하게 출력할 수 있습니다. 인게임 볼륨으로 렌더링되기 때문에, 효과음만 포함된 게임 플레이 영상과 결합할 때 자연스러운 볼륨 밸런스를 맞출 수 있습니다. 이런 사용 사례를 고려하여 여러 Playlist Container와 Event를 동시에 렌더링하는 기능도 제공합니다.
이 글이 음악 구현을 효율화하는 데 도움이 되길 바랍니다!
댓글