오프라인 음악 렌더링 도구

Wwise에 대한 팁과 도구

더욱 효율적으로 음악 구현하기

Wwise를 사용해 게임 음악을 구현할 때 음악 루프가 자연스럽게 이어지는지 확인하기 위해서는 곡 전체를 재생해야 합니다. 예를 들어 2분 길이의 음악 구간이 있다면 루프 지점이 원활하게 이어지는지 확인하기 위해 전체 2분을 모두 들어봐야 하죠.

상호작용 음악에서 Segment 전환이나 Transition을 확인할 때도 마찬가지입니다. 특히 긴 트랙의 후반부에서 발생하는 전환의 경우, 문제가 없다면 한 번의 확인으로 충분할 수 있습니다. 하지만 문제가 있을 경우 설정이나 애셋을 수정한 뒤 긴 구간을 다시 재생해서 확인해야 합니다.

이러한 반복적인 확인 작업은 시간이 많이 들고 정신적으로도 좀 힘들죠. 시간이 부족할 경우 품질 저하로 이어질 수 있습니다.

이러한 문제를 해결하기 위해 플래티넘게임즈는 Wwise의 오프라인 렌더링 API를 활용해 오디오 루프와 전환을 보다 효율적으로 확인할 수 있는 도구인 'Music Render'를 독자적으로 개발했습니다. 이 글에서는 이 도구의 사용 예시와 기술적 구성을 소개합니다.

 

Music Render: 사용 예시

아래는 Wwise 예제 프로젝트 'Cube'를 기반으로 Music Render가 Wwise를 활용한 음악 구현에서 흔히 발생하는 문제들을 어떻게 효과적으로 해결하는지 보여주는 실용적인 사례들입니다.

 

예시 1: 단순 루프

Story Playlist Container는 약 1분 길이의 단일 Music Segment로 구성된 단순 루프입니다. 루프를 확인하려면 1분 동안 재생해야 하죠.

image1

 

바로 여기서 Music Render가 빛을 발합니다. Wwise Playlist 메뉴에서 이 도구를 활성화 한뒤 다음의 매개 변수를 설정합니다.

  • 렌더링 시간: 루프 확인이 필요한 길이 (1~10분)
  • WAV 파일을 열 응용 프로그램: 예를 들어 MAGIX의 SOUND FORGE를 지정.

image2

 

[렌더링 시작]을 클릭하면 SoundBank 생성과 렌더링이 자동으로 수행되며, 완료 후 WAV파일이 SOUND FORGE에서 열립니다. 루프 지점부터 음악 세그먼트를 바로 재생하여 루프를 빠르게 확인할 수 있습니다.

image3

 

예시 2: 복잡한 Playlist

Explore Playlist Container에는 여러 개의 Music Segment가 포함되어 있습니다. 이 도구가 없다면 각 Music Segment의 전환에 문제가 없는지 확인하기 위해 전체를 오랜 시간 동안 재생해야 합니다.

image5

 

렌더링 중에 Music Render는 생성된 파형 파일의 세그먼트 전환 지점에 마커를 자동으로 추가합니다. 이렇게 하면 전체 트랙을 처음부터 끝까지 들을 필요 없이 마커 위치에서 재생을 시작해 전환 지점을 바로 확인할 수 있습니다.

image6

 

예시 3: 상호작용 전환

Wwise 201의 Music Switch Container는 Gameplay_Switch 스위치에 따라 음악이 Combat 또는 Explore로 전환됩니다.

image8

 

전환을 확인하려면 보통 트랙을 수동으로 전환해야 하지만 Music Render를 사용하면 이러한 전환이 자동화된 이벤트를 생성하고 렌더링함으로써 보다 효율적으로 확인할 수 있습니다.
다음과 같은 구성으로 예제 이벤트를 생성합니다:

  • Switch Container 재생
  • 15초마다 스위치 값 변경

image9

 

Music Render를 통해 해당 이벤트를 오프라인으로 렌더링하면, 트랙이 15초 간격(15초, 30초, 45초, ...)으로 전환되는 음악 파형이 생성됩니다. 부자연스러운 전환이 있을 경우, 트랜지션이나 애셋을 수정한 다음 다시 렌더링하여 수정 사항을 확인할 수 있습니다. 이러한 자동화를 통해 수동으로는 재현하기 어려운 문제도 확실하게 검증하고 수정할 수 있습니다.

image10

 

기술 구성

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를 동시에 렌더링하는 기능도 제공합니다.

이 글이 음악 구현을 효율화하는 데 도움이 되길 바랍니다!

田中 直人(나오토 다나카)

田中 直人(나오토 다나카)

나오토는 1996년부터 작곡가로서 수많은 게임 오디오 프로젝트에 참여해 왔습니다. 2013년에는 '메탈 기어 라이징 리벤젼스(Metal Gear Rising: Revengeance)'에서 음악 디렉터로 활동하며, Wwise 초기 시절의 외부 작곡가들과의 협업을 통해 상호작용 오디오 도입을 적극적으로 추진했습니다. 그는 2022년 '베요네타(Beyonetta) 3'의 음악 구현을 비롯해 사운드 기술 아티스트로서 플래티넘게임즈(PlatinumGames)의 다양한 프로젝트에 참여했습니다.

댓글

댓글 달기

이메일 주소는 공개되지 않습니다.

다른 글

합성 만으로 빗소리 만들기

몇 년 전 저는 원하는 모든 사운드를 합성할 수 있을까 하는 궁금증이 생겼습니다. 바람, 새의 노랫소리, 곤충 소리 등 다양한 자연 소리를 합성하기 시작했죠. 이런 작업에서는...

3.9.2020 - 작성자: 알렉산더 킬코(ALEKSANDR KHILKO)

새로워진 Wwise Audio Lab(WAL)을 소개합니다

Wwise Audio Lab(와이즈 오디오 랩, WAL)은 Unreal Engine 4를 통해 오픈 소스로 개발된 게임 형식의 3D 환경이며 Wwise 런처를 통해 제공됩니다....

19.1.2022 - 작성자: 데미안 캐스트바우어 (Damian Kastbauer)

Wwise 2022.1 새로운 기능

Wwise 2022.1이 출시되었으며 Audiokinetic 런처를 통해 다운받으실 수 있습니다. 이 버전이 제공하는 새로운 기능을 간략하게 소개해드리려고 합니다....

16.11.2022 - 작성자: Audiokinetic (오디오키네틱)

Wwise Spatial Audio 2023.1의 새로운 기능 | 개선된 Aux Send Model

Wwise 2023.1에서 새로 제공되는 수많은 기능의 목록을 살펴보셨다면 아마 '개선된 Aux Send Model'이라는 흥미로운 문구를 발견하셨을 겁니다. 도대체 이게 무슨...

14.12.2023 - 작성자: Nathan Harris

Wwise 출시 주기 변경 | Sim-Patch 출시 및 언리얼 엔진 프리뷰 개발 지원

이 글에서는 지난 몇 달 동안 Audiokinetic의 개발 프로세스에 적용된 몇몇 변경 사항을 공유하려고 합니다. 이러한 변경 사항은 Wwise가 더 자주 제공되어 더 빨리 다음...

28.5.2024 - 작성자: 기욤 르노 (Guillaume Renaud)

ReaWwise를 사용한 ReaScript(Lua)에서의 WAAPI

ReaWwise에서 잘 알려지지 않은 기능 중 하나는 원시적 WAAPI 함수를 REAPER에 노출하여 사용자 정의 ReaScript에서 사용할 수 있다는 것입니다. 이 블로그...

20.11.2024 - 작성자: 앤드류 코스타 (Andrew Costa)

다른 글

합성 만으로 빗소리 만들기

몇 년 전 저는 원하는 모든 사운드를 합성할 수 있을까 하는 궁금증이 생겼습니다. 바람, 새의 노랫소리, 곤충 소리 등 다양한 자연 소리를 합성하기 시작했죠. 이런 작업에서는...

새로워진 Wwise Audio Lab(WAL)을 소개합니다

Wwise Audio Lab(와이즈 오디오 랩, WAL)은 Unreal Engine 4를 통해 오픈 소스로 개발된 게임 형식의 3D 환경이며 Wwise 런처를 통해 제공됩니다....

Wwise 2022.1 새로운 기능

Wwise 2022.1이 출시되었으며 Audiokinetic 런처를 통해 다운받으실 수 있습니다. 이 버전이 제공하는 새로운 기능을 간략하게 소개해드리려고 합니다....