Перейти к содержанию

Рекомендуемые сообщения

 

Presence Audio - это небольшая open-source библиотека для игровых движков. Главная фишка: объёмный звук с помощью трассировки пути в реальном времени. Простая интеграция, открытый код (лицензия MIT), физически точные отражения, окклюзия и реверберация, зависящая от материалов.


Для кого этот гайд?

  • Для модмейкеров, которые хотят вывести звук в своих сборках на новый уровень.
  • Для разработчиков, которые ищут простой и мощный инструмент для 3D-аудио.
  • Для всех, кому интересно, как работает трассировка звука внутри X-Ray.


Гайд — ниже. Если появятся вопросы по интеграции — добро пожаловать в Discord и на GitHub.

Спойлер
  • Шаг первый: Активация EAX

    В самом начале работы нам необходимо разобраться с самой ключевой системой — EAX.
    Presence Audio SDK — это мозг, который занимается расчетами, но сердцем эффектов реверберации является EAX.
    В X-Ray Engine 1.0 EAX доступен, но не используется. В оригинальном исполнении
    EAX использовался для создания статических преднастроенных зон реверберации, расставляемых вручную, мы же
    будем заменять эту систему на динамическую, работающую при помощи трассировки лучей, но для начала нам
    необходимо заставить EAX работать. Дело в том, что код X-Ray Engine устанавливает статус EAX как
    «невостребованный» по умолчанию, нам необходимо исправить это и заставить его использовать EAX всегда, когда он доступен.
    Для того чтобы активировать использование EAX, необходимо внутри структуры ALDeviceDesc в
    xrSound\OpenALDeviceList.h найти флаг eax_unwanted, который и делает EAX «невостребованным».
    Его необходимо удалить, как и все его использования, для того чтобы EAX использовался всегда.

    После этого можно проверить работу EAX.
    Для проверки мы просто установим очень сильный эффект эха.
    Внутри xrSound\SoundRender_Core.cpp найдите метод CSoundRender_Core::i_eax_listener_set — он отправляет
    параметры EAX в звуковой драйвер, просто замените его код на данный:

    
    void CSoundRender_Core::i_eax_listener_set(CSound_environment* _E)
    {
      VERIFY(bEAX);
      // _E больше не используется – параметры жёстко заданы для проверки
      EAXLISTENERPROPERTIES ep;
    
      // === Тестовые значения для отчётливого эха ===
      ep.lRoom = 0; // максимальный уровень эффекта комнаты (0 дБ)
      ep.lRoomHF = 0; // то же на высоких частотах
      ep.flRoomRolloffFactor = 0.0f; // без затухания реверберации с расстоянием
      ep.flDecayTime = 5.0f; // длительное время спада (5 секунд)
      ep.flDecayHFRatio = 1.0f; // ВЧ и НЧ затухают одинаково
      ep.lReflections = 0; // ранние отражения на максимуме
      ep.flReflectionsDelay = 0.2f; // заметная задержка первого отражения (200 мс)
      ep.lReverb = 0; // поздняя реверберация на максимуме
      ep.flReverbDelay = 0.1f; // задержка поздней реверберации (100 мс)
      ep.dwEnvironment = EAXLISTENER_DEFAULTENVIRONMENT; // не используется в этом коде
      ep.flEnvironmentSize = 50.0f; // большое помещение (50 метров)
      ep.flEnvironmentDiffusion = 1.0f; // максимальная диффузия
      ep.flAirAbsorptionHF = -1.0f; // небольшое поглощение высоких частот
      ep.dwFlags = EAXLISTENER_DEFAULTFLAGS; // стандартные флаги
    
      u32 deferred = bDeferredEAX ? DSPROPERTY_EAXLISTENER_DEFERRED : 0;
    
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOM, &ep.lRoom, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOMHF, &ep.lRoomHF, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, &ep.flRoomRolloffFactor, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_DECAYTIME, &ep.flDecayTime, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_DECAYHFRATIO, &ep.flDecayHFRatio, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REFLECTIONS, &ep.lReflections, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, &ep.flReflectionsDelay, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REVERB, &ep.lReverb, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REVERBDELAY, &ep.flReverbDelay, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, &ep.flEnvironmentDiffusion, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, &ep.flAirAbsorptionHF, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_FLAGS, &ep.dwFlags, sizeof(DWORD));
    }

    Если вы услышали гулкое эхо — вы на правильном пути, а значит, можно продолжать.


  • Шаг второй: Добавление кода PresenceAudioSDK

    Скачайте содержимое репозитория Presence Audio.
    Данный туториал привязан к версии Presence Audio ver. 0.4 InDev, поэтому используйте данную ссылку на репозиторий, с привязкой к коммиту:
    https://github.com/Presence-Collaboratory/Presence-3D-Audio/tree/8b0710e8ae884616bd813dc3cdf5564b1cf8fafa
    Склонируйте или скачайте содержимое, можете использовать данную ссылку для скачивания в zip:
    https://github.com/Presence-Collaboratory/Presence-3D-Audio/archive/8b0710e8ae884616bd813dc3cdf5564b1cf8fafa.zip

    Скопируйте папку PresenceAudioSDK из репозитория библиотеки в ваш код, рекомендуется папка third-party\include,
    после чего добавьте проект PresenceAudioSDK\Source\PresenceAudioSDK.vcxproj в ваше решение X-Ray Engine, а также скорректируйте
    каталоги включения и выходные каталоги сборки — lib- и dll-библиотеки должны собираться в те же директории, что и остальные библиотеки игры.
    Обязательно добавьте PresenceAudioSDK в зависимости проекта xrGame, для того чтобы наша библиотека собиралась автоматически.
    (Обозреватель решений -> проект xrGame -> Ссылки -> ПКМ по надписи «Ссылки» -> Добавить ссылку -> Отметьте галочкой PresenceAudioSDK)


  • Шаг третий: Добавление реализации провайдеров данных для библиотеки

    Возьмите из демонстрационного репозитория интеграции каталог src/xr_3da/xrGame/PresenceAudioIntegration/ — в нём лежат четыре файла,
    реализующие мост между движком и библиотекой.
    Данные файлы уже имеют базовую реализацию всего необходимого кода для работы библиотеки.
    Данный код был написан для X-Ray Engine 1.0, поэтому если ваша кодовая база
    отличается от него, то вам придется внести изменения.

    Скопируйте данную папку в папку кода с файлами xrGame и добавьте эти файлы кода в проект.


  • Шаг четвертый: Интеграция методов в xrSound

    4.1 Для того чтобы Presence Audio могла отправлять в OpenAL драйвер значения параметров EAX для работы эффектов эха, нам необходимо создать
    в абстрактном классе CSound_manager_interface в xrSound\Sound.h виртуальный метод commit_eax.

    Добавьте в публичные методы чисто виртуальный метод с такой сигнатурой:

    
    virtual void commit_eax(const SEAXEnvironmentData * data) = 0;

    Данный метод имеет в аргументах структуру SEAXEnvironmentData, которая и будет нести в себе значения параметров EAX, её реализация
    находится внутри xr_3da\xrGame\PresenceAudioIntegration\Sound_environment_common.h, но для того чтобы не подключать данный файл
    в xrSound\Sound.h, добавьте прямую декларацию данной структуры к другим прямым декларациям внутри данного файла.
    Добавьте объявление в начало файла:

    
    struct SEAXEnvironmentData;


    После того как вы добавили абстрактный метод commit_eax, перейдите в класс-наследник CSoundRender_Core (xrSound\SoundRender_Core.h).
    Данный класс реализует весь базовый функционал библиотеки xrSound, а также необходимый для нас код работы с EAX.

  • Для начала вам необходимо найти уже упомянутый нами в первом шаге void CSoundRender_Core::i_eax_listener_set (xrSound\SoundRender_Core.cpp).
    В оригинальном коде X-Ray Engine данный метод является частью старой системы EAX, работающей на фиксированных,
    устанавливаемых вручную в редакторе зонах. Вам необходимо избавиться от данного кода, но для краткости туториала
    просто закомментируйте тело CSoundRender_Core::i_eax_listener_set, чтобы избежать конфликтов с нашим новым commit_eax.

    Далее создайте реализацию commit_eax внутри класса CSoundRender_Core:

    Добавьте в начало xrSound\SoundRender_Core.h включения файлов:

    
    #include <mmsystem.h>
    #include "../xr_3da/xrGame/PresenceAudioIntegration/Sound_environment.h"

    Создайте объявление внутри класса CSoundRender_Core

    
    public:
    virtual void commit_eax(const SEAXEnvironmentData * data);

    После этого добавьте в xrSound\SoundRender_Core.cpp

    
    void CSoundRender_Core::commit_eax(const SEAXEnvironmentData * data)
    {
      if (!bEAX || !data)
      	return;
      
      EAXLISTENERPROPERTIES ep;
      ZeroMemory(&ep, sizeof(ep));ep.lRoom = data->lRoom;
      ep.lRoomHF = data->lRoomHF;
      ep.flRoomRolloffFactor = data->flRoomRolloffFactor;
      ep.flDecayTime = data->flDecayTime;
      ep.flDecayHFRatio = data->flDecayHFRatio;
      ep.lReflections = data->lReflections;
      ep.flReflectionsDelay = data->flReflectionsDelay;
      ep.lReverb = data->lReverb;
      ep.flReverbDelay = data->flReverbDelay;
      ep.flEnvironmentSize = data->flEnvironmentSize;
      ep.flEnvironmentDiffusion = data->flEnvironmentDiffusion;
      ep.flAirAbsorptionHF = data->flAirAbsorptionHF;
      ep.dwFlags = data->dwFlags;
      
      u32 deferred = bDeferredEAX ? DSPROPERTY_EAXLISTENER_DEFERRED : 0;
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOM, &ep.lRoom, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOMHF, &ep.lRoomHF, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, &ep.flRoomRolloffFactor, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_DECAYTIME, &ep.flDecayTime, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_DECAYHFRATIO, &ep.flDecayHFRatio, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REFLECTIONS, &ep.lReflections, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, &ep.flReflectionsDelay, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REVERB, &ep.lReverb, sizeof(LONG));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_REVERBDELAY, &ep.flReverbDelay, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, &ep.flEnvironmentSize, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, &ep.flEnvironmentDiffusion, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, &ep.flAirAbsorptionHF, sizeof(float));
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, deferred | DSPROPERTY_EAXLISTENER_FLAGS, &ep.dwFlags, sizeof(DWORD));if (bDeferredEAX)
      i_eax_set(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS, NULL, 0);
    }


    4.2 Для корректной работы нашего кода нам необходимо добавить необходимые методы для
    функционала звуковой окклюзии, но саму звуковую окклюзию мы будем внедрять в завершающем этапе туториала.

    Внутрь класса CSoundRender_Core (xrSound\SoundRender_Core.h) в самый конец добавьте:

    
    public:
    Presence::ISoundOcclusionCalculator* m_pOcclusion = nullptr;
    void SetOcclusion(Presence::ISoundOcclusionCalculator* pOcc) { m_pOcclusion = pOcc; }

    В качестве проверки правильности совершенной работы можете попробовать собрать исходный код — ошибок быть не должно.
    Если код собирается успешно, то можно переходить к следующему шагу.


  • Шаг пятый: Жизненный цикл класса CSoundEnvironment

    Для работы эффектов эха нам необходимо обеспечить использование движком класса CSoundEnvironment,
    объявленного в PresenceAudioIntegration/Sound_environment.h.

    Для краткости и наглядности данного туториала мы будем интегрироваться в логику жизненного цикла игрового уровня.

    Внутри реализации CLevel (xr_3da\xrGame\Level.cpp) мы будем добавлять создание, обновление и удаление нашего класса CSoundEnvironment.
    Таким образом мы будем создавать экземпляр нашего класса только при загрузке уровня, обновлять эхо внутри его цикла обновления,
    а удалять экземпляр класса только тогда, когда наш игровой уровень удаляется.

    Для начала добавьте включение нашего заголовочного файла CSoundEnvironment в начале xr_3da\xrGame\Level.cpp после всех остальных
    включений:

    
    #include "PresenceAudioIntegration/Sound_environment.h"

    Затем для простоты и наглядности создайте глобальный экземпляр CSoundEnvironment. Так как в данном туториале мы будем использовать этот экземпляр только в рамках этой одной единицы трансляции, проблем с жизненным циклом не будет.
    Добавьте данное объявление после включения заголовочного файла:

    
    CSoundEnvironment * g_SoundEnvironment = nullptr;

    Теперь мы можем использовать код CSoundEnvironment в нашем Level.cpp.

    В конец конструктора класса CLevel (CLevel::CLevel()) добавьте
    инициализацию нашего g_SoundEnvironment и вызов метода OnLevelLoad():

    
    if (!g_dedicated_server)
    {
      g_SoundEnvironment = xr_new<CSoundEnvironment>();
      g_SoundEnvironment->OnLevelLoad();
    }

    После этого в начало деструктора класса CLevel (CLevel::~CLevel()) добавьте
    вызов OnLevelUnload() и удаление экземпляра класса g_SoundEnvironment

    
    if (!g_dedicated_server)
    {
      g_SoundEnvironment->OnLevelUnload();
      xr_delete(g_SoundEnvironment);
    }

    Далее внутри void CLevel::OnFrame() необходимо добавить вызов обновления нашего g_SoundEnvironment.
    В теле данного метода создайте вызов обновления:

    
    if (!g_dedicated_server)
    	g_SoundEnvironment->Update();

    После этого соберите движок и проверьте работоспособность эха. Пока что мы интегрировали только эффект эха, звуковая окклюзия будет в следующем шаге.
    Если код заработал — мы в одном шаге от завершения!


  • Шаг шестой: Окклюзия между слушателем и источником звука

    Для того чтобы заставить работать расчет громкости звука в зависимости от того, какие препятствия преграждают слышимость,
    нам необходимо воспользоваться уже внедренным хуком, который мы добавили в CSoundRender_Core в четвертом шаге в коде воспроизведения звука.

    Внутри класса CSoundRender_Emitter в файле xrSound\SoundRender_Emitter_FSM.cpp мы должны заменить оригинальный код расчета окклюзии
    в момент старта воспроизведения звука на обновленный.

    Внутри метода CSoundRender_Emitter::update происходит выбор поведения при воспроизведении
    звука в зависимости от его состояния внутри switch-case конструкции.
    Это первое место, где мы должны произвести изменения.

    Необходимо в блоках case stStarting: и case stStartingLooped: найти строчки расчета громкости окклюзии при помощи старого метода get_occlusion
    и заменить на данный блок кода. Вы можете вынести этот код в отдельный метод для избежания дублирования кода, но для краткости и простоты туториала мы просто заменим код.

    Найдите в данных блоках строчки (в каждом блоке по строке):

    
    occluder_volume = SoundRender->get_occlusion(p_source.position, .2f, occluder);

    И замените каждую из них на данный код:

    
    {
      Fvector l_pos = SoundRender->listener_position();
      Fvector s_pos = p_source.position;
    
      float fSomOcclusion = SoundRender->get_occlusion_to(l_pos, s_pos);
      if (fSomOcclusion < 0.01f)
      {
      	occluder_volume = 0.0f;
      }
      else
      {
        if (SoundRender->m_pOcclusion)
        {
          Presence::float3 listener(l_pos.x, l_pos.y, l_pos.z);
          Presence::float3 source(s_pos.x, s_pos.y, s_pos.z);
          occluder_volume = SoundRender->m_pOcclusion->CalculateOcclusion(listener, source);
        }
        else
        {
          occluder_volume = SoundRender->get_occlusion(p_source.position, .2f, occluder);
        }
      }
    }

    Данный код комбинирует старую звуковую окклюзию при помощи специальной SOM геометрии с динамической окклюзией через трассировку лучей.

    Далее в том же файле кода найдите метод CSoundRender_Emitter::update_culling.
    В нем необходимо заменить строку:

    
    volume_lerp(occluder_volume, SoundRender->get_occlusion(p_source.position, .2f, occluder), 1.f, dt);

    На данный блок кода:

    
    float target_occlusion = 1.0f;
    Fvector l_pos = SoundRender->listener_position();
    Fvector s_pos = p_source.position;
    
    float fSomOcclusion = SoundRender->get_occlusion_to(l_pos, s_pos);
    if (fSomOcclusion < 0.01f)
    {
      target_occlusion = 0.0f;
    }
    else
    {
      if (SoundRender->m_pOcclusion)
      {
        Presence::float3 listener(l_pos.x, l_pos.y, l_pos.z);
        Presence::float3 source(s_pos.x, s_pos.y, s_pos.z);
        target_occlusion = SoundRender->m_pOcclusion->CalculateOcclusion(listener, source);
      }
      else
      {
        target_occlusion = SoundRender->get_occlusion(p_source.position, .2f, occluder);
      }
    }
    
    volume_lerp(occluder_volume, target_occlusion, 10.f, dt);

    Теперь все основные этапы закончены! Поздравления!


Бонусные этапы


 

  • Используйте кастомные материалы

    Код интеграции, который мы добавили в xrGame, поддерживает загрузку кастомных материалов из конфига.

    Добавьте в каталог gamedata/config/ файл с названием presence_audio_materials.ltx

    Теперь вы можете добавлять кастомные материалы в данный конфиг.

    Скопируйте в него данный текст, новые материалы можете добавлять по аналогии с имеющимися:

    
    ; ==============================================================================
    ; Presence Audio SDK - Custom Material Properties
    ; ==============================================================================
    ; transmission: [0.0 - 1.0] Сколько звука проходит сквозь стену (0 - глухая стена)
    ; reflectivity: [0.0 - 1.0] Сила отражения (эхо). 1.0 - металл / кафель, 0.0 - ковер
    ; absorption: [0.0 - 1.0] Поглощение звука. 1.0 - полная тишина при ударе
    ; rt60_weight: [0.0 - 1.0] Вклад в длину реверберации.
    ; ==============================================================================
    
    ; Пример тонкой деревянной двери (пропускает звук, немного глушит)
    [materials\door_wood]; Название должно быть таким же, какое вы видите в Shader editor (папка + название)
    transmission = 0.45
    reflectivity = 0.20
    absorption = 0.10
    rt60_weight = 0.15
    
    ; Пример металлической решетки (почти полностью пропускает звук, но звенит)
    [materials\grate]
    transmission = 0.95
    reflectivity = 0.60
    absorption = 0.0
    rt60_weight = 0.3
    
    ; Пример толстого бетона (глухой, сильное эхо)
    [materials\concrete_wall]
    transmission = 0.01
    reflectivity = 0.75
    absorption = 0.1
    rt60_weight = 0.9
    
    ; Пример стекла (хрупкое, звонкое, пропускает ВЧ)
    [materials\glass]
    transmission = 0.6
    reflectivity = 0.5
    absorption = 0.05
    rt60_weight = 0.4

     

  • Обновите OpenAL

    OpenAL из X-Ray Engine является устаревшим, а также имеет проблемы с позиционированием звука, компрессией
    и звучанием эффектов EAX, поэтому стоит его обновить.

    Можете использовать открытую реализацию OpenAL Soft.

    Скачайте последний актуальный релиз (желательно OpenAL Soft + HRTF) с официального репозитория GitHub по ссылке:
    https://github.com/kcat/openal-soft/releases

    Скопируйте OpenAL32.dll и alsoft.ini из папки специфичной платформе сборки вашего движка в вашу папку bin.

 

Спойлер

Ниже ссылки на GitHub коммиты по каждому шагу интеграции Presence Audio в репозиторий X-Ray Engine 1.0

Шаг первый (клик)
Шаг второй (клик)
Шаг третий (клик)
Шаг четвертый (клик)
Шаг пятый (клик)
Шаг шестой (клик)
 

Спойлер

Ссылка на папку bin для S.T.A.L.K.E.R. Тень Чернобыля с уже интегрированным Presence Audio
Необходимо скопировать содержимое архива в папку с игрой, вместо оригинальной папки bin
https://drive.google.com/file/d/10QJ86HGF25iomRxKIc5lriQYmJH_kqa3/view?usp=sharing

Спойлер

ВК - https://vk.com/xplatform
Discord - https://discord.gg/74bTSBPSCz

По любым проблемам обращайтесь на сервер в Discord

Спойлер

Номер карты сбер: 2202 2083 7726 1566

 

Изменено пользователем PresenceCoLab

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти

  • Последние посетители   1 пользователь онлайн

Важная информация

Мы разместили cookie-файлы на ваше устройство, чтобы помочь сделать этот сайт лучше. Вы можете изменить свои настройки cookie-файлов, или продолжить без изменения настроек. Оставаясь на сайте, вы подтверждаете свое согласие на их использование. Политика конфиденциальности | Условия использования