본문으로 건너뛰기

STCoreV2

모듈식 음향 경로 탐색과 lock-free 데이터 전달을 채택한 차세대 Sound Tracing 코어.

STCoreV2는 메시·재질·청취자·음원으로부터 임펄스 응답(IR)과 공간 오디오를 실시간 합성하는 C++ 라이브러리입니다.

개요

항목
기준 브랜치feat/lock-free-hybrid-v2-reverb (lock-free reverb)
주 언어C++ (C++17 빌드, C API 노출)
빌드 시스템CMake 3.22+
플랫폼macOS · Windows · Linux
Web 빌드Emscripten 지원 (EMSCRIPTEN_KEEPALIVE export)
가속기내장 BVH 또는 External callback (게임 엔진 BVH 등)
Max Path Depth16 (EXA_MAX_DEPTH)
테스트Google Test (unit + benchmark)
상태활성 개발

아키텍처

Scene


SceneSnapshot ── lock-free per-tick snapshot
│ (TripleBuffer · POD · immutable)

┌─────────────────────────────────────────────┐
│ Propagation │
│ ├ IAccelerator │
│ │ ├ Internal BVH │
│ │ └ ExternalAccelerator (callback) │
│ └ Path Modules (IPathModule) │
│ ├ SpecularReflectionModule │
│ ├ UTDDiffractionModule │
│ ├ DiffuseOffsetModule │
│ └ StaticReverbModule │
│ (+ ScatterHandoff, ComparisonReport) │
└─────────────────────────────────────────────┘


Auralizator (filter / frequence / HRTF / states)


Audio Output

엔진은 크게 네 계층입니다.

  • Scene → Snapshot — 매 틱 장면 상태가 POD 기반 SceneSnapshot으로 떠지고, TripleBuffer를 통해 propagation·렌더 스레드에 lock-free로 전달됩니다.
  • Propagation (모듈식)IPathModule 공통 인터페이스 위에 4종 모듈이 동시 실행되며, 모듈별로 알고리즘 교체·수치 비교가 가능합니다. 가속기는 내장 BVH 외에도 호스트 측 BVH를 콜백으로 위임할 수 있습니다.
  • Auralizator — 추적된 경로를 IR로 합성하고, 필터·주파수 분해·HRTF를 적용하여 공간 오디오를 생성합니다.
  • Audio Output — 채널 매핑 후 호출자에게 반환합니다.

모듈 구성

exaSound/
├── src/
│ ├── core/ # 엔진 코어
│ │ ├── EngineConfig # 엔진 설정
│ │ ├── SceneSnapshot # immutable per-tick snapshot
│ │ ├── SnapshotBuilder # 스냅샷 빌더
│ │ ├── ExternalAccelerator # 외부 BVH 콜백
│ │ ├── IAccelerator # 가속기 추상
│ │ ├── Handle / Ref / FixedPool / PoolAllocator
│ │ ├── ThreadAffinity / Telemetry
│ │ └── PropagationResult
│ ├── propagation/
│ │ ├── Propagator # 최상위 dispatch
│ │ ├── RGC # Ray Generation 클러스터
│ │ ├── module/ # Path Module 추상화
│ │ │ ├── IPathModule # 공통 인터페이스
│ │ │ ├── PathModuleRegistry
│ │ │ ├── reflection/ # SpecularReflectionModule
│ │ │ ├── diffraction/ # UTDDiffractionModule
│ │ │ ├── diffuse/ # DiffuseOffsetModule
│ │ │ └── reverb/ # StaticReverbModule
│ │ ├── UTDDiffraction.hpp # UTD 공식
│ │ └── Ray/ # Ray·평면 유틸
│ ├── auralizator/ # 음향화 (이전 auralization)
│ │ ├── core / filter / frequence / HRTF / states
│ ├── scene/
│ │ ├── SoundObject # Object·Mesh·Preprocessing
│ │ └── BVH # Native BVH/TLAS
│ ├── math/, utils/, objects/, config/
│ ├── exasound.h # C++ 메인 헤더
│ └── exasoundC.h # **공개 C API**
├── tests/ # Google Test
└── demo/ # 데모

Public C API

C 링키지 API (exasoundC.h, 약 120개 export)로 공개됩니다.

카테고리대표 함수
라이프사이클exaInit, exaReset, exaGetVersion, exaGetPathTypeCount
SceneexaNewScene, exaTickScene, exaSceneAddObject/Source/Listener
ObjectexaNewObject, exaObjectSetPosition/Rotation/Scale/Mesh, exaObjectSetUpdateType
MeshexaNewMesh, exaMeshSetData, exaMeshUpdateVertices, exaMeshRefit, exaMeshSetMaterial
MaterialexaAddSoundMaterial, exaSetSoundMaterial
SoundSourceexaNewSoundSource, exaSoundSourceSetPosition/Direction/Velocity/Intensity, exaSoundSourceSetRayCount/GetRayCount, exaSoundSourceSetDepth/GetDepth, exaSoundSourceSetReverbSendDb/GetReverbSendDb, exaSoundSourceSetPathEnable/IsPathEnabled
Listener (기본)exaNewListener, exaListenerSetPosition/Orientation/Velocity, exaListenerSetRayCount/RayDepth, exaListenerSetOption/GetOption (sceneRatio 등), exaListenerSetCoordinateBasis/GetCoordinateBasis
Listener (HRTF)exaInit에서 default HRTF를 전역 1회 로드하고 listener renderer가 공유
RendererexaCreateRenderer, exaRenderSound, exaRemoveRenderer
결과 조회exaGetValidPathCount, exaGetValidPaths, exaGetSortedIRDatas
진단·시각화exaPropagatorGetGuidePlanes/MirrorPositions, exaPropagatorGetProfile, exaGetStatistics, exaGetMemoryTraceSnapshot, exaGetLastError

시작하기

요구사항

  • CMake 3.22 이상
  • C++17 호환 컴파일러
  • macOS · Windows · Linux

빌드

cd exaSound
cmake -S . -B build
cmake --build build

테스트 포함:

cmake -S . -B build -DBUILD_TESTS=ON
cmake --build build --target unit_tests
./build/tests/unit/unit_tests

최소 사용 시나리오 (의사 코드)

#include "exasoundC.h"

// 1. 엔진 초기화
exaInit();

// 2. 장면 생성
int sceneID = exaNewScene();

// 3. 메시 등록 + 재질 부여
int meshID = exaNewMesh();
exaMeshSetData(meshID, vertices, vertexCount, indices, indexCount);
exaMeshSetMaterial(meshID, materialIndex);

// 4. 오브젝트에 메시를 붙이고 장면에 추가
int objID = exaNewObject();
exaObjectSetMesh(objID, meshID);
exaObjectSetPosition(objID, 0.f, 0.f, 0.f);
exaSceneAddObject(sceneID, objID);

// 5. 음원·청취자 설정
int srcID = exaNewSoundSource();
exaSoundSourceSetPosition(srcID, 1.f, 1.f, 0.f);
exaSoundSourceSetIntensity(srcID, 1.0f);
exaSceneAddSource(sceneID, srcID);

int listenerID = exaNewListener();
exaListenerSetPosition(listenerID, -1.f, 1.f, 0.f);
exaListenerSetRayCount(listenerID, 4096);
exaListenerSetRayDepth(listenerID, 16); // up to EXA_MAX_DEPTH = 16
exaSceneAddListener(sceneID, listenerID);

// 6. 매 프레임 시뮬레이션 + 오디오 렌더
for (;;) {
exaTickScene(sceneID, deltaTime);
exaRenderSound(/* 렌더 인자 */);
// exaGetValidPaths / exaGetSortedIRDatas 로 결과 조회
}

// 7. 정리
exaReset();

위 코드는 API 흐름을 보여주는 의사 예제입니다. 실제 시그니처는 exasoundC.h에서 확인하세요.

핵심 개념

Path Module 구조

전파 알고리즘은 4종 모듈로 분리되어 내부 파이프라인에서 동작합니다.

모듈경로 유형위치
SpecularReflectionModule정반사propagation/module/reflection/
UTDDiffractionModule회절(UTD)propagation/module/diffraction/
DiffuseOffsetModule산란propagation/module/diffuse/
StaticReverbModule정적 잔향propagation/module/reverb/

각 모듈은 IPathModule을 구현하며 두 단계로 동작합니다.

  1. Phase 1 — buildSetupPlanes : Guide ray 결과로부터 SetupPlane 구성
  2. Phase 2 — validatePaths : 경로 추적·검증, 유효 경로를 출력 버퍼에 기록

Specular → Diffuse 간에는 ScatterHandoffEntry로 path 상태가 전달됩니다.

Lock-free 스냅샷 (SceneSnapshot)

장면 상태는 매 틱 immutable POD 스냅샷으로 떠집니다.

  • 모든 구조는 평탄 배열·non-virtual·heap 포인터 없음 → TripleBuffer 슬롯에 값 복사 가능
  • propagation·audio 스레드가 별도 슬롯을 읽어 lock-free로 동작
  • 지오메트리는 별도 BVH 더블 버퍼로 관리 (Phase 3)

이 구조 덕분에 다중 스레드에서 mutex 없이 안전하게 시뮬레이션·렌더가 진행됩니다.

Material 모델

SoundTriangle 단위에 **흡수율(absorption)**과 **투과율(transmission)**을 직접 저장합니다. 별도 Material ID/포인터를 두지 않으며, 반사 계산은 다음 규칙입니다.

reflection = 1 - (absorption + transmission)

ExaRayHit 구조에는 materialId 필드가 노출되어 ray cast 시 어떤 재질에 충돌했는지 직접 식별할 수 있습니다.

Listener Ray Count · Ray Depth

청취자 기준 guide ray 개수와 최대 깊이를 설정합니다. direct/reflection/diffraction 경로 발견 품질에 직접 영향을 주는 전역 품질 축입니다.

함수효과
exaListenerSetRayCount(id, n)광선 수 설정
exaListenerSetRayDepth(id, d)최대 깊이 (1 ≤ d ≤ EXA_MAX_DEPTH = 16)

현재 빌드의 path depth 상한은 EXA_MAX_DEPTH = 16입니다.

Source Reverb Ray Count · Ray Depth

source reverb ray는 listener ray와 별도의 source-side late reverb 비용 축입니다. multi-source에서는 source 수와 곱해지므로 listener ray보다 낮은 budget에서 시작합니다.

함수효과
exaSoundSourceSetRayCount(id, width, height)source-side reverb ray grid 설정
exaSoundSourceGetRayCount(id, outWidth, outHeight)source-side reverb ray grid 조회
exaSoundSourceSetDepth(id, depth)source-side reverb ray depth 설정
exaSoundSourceGetDepth(id, outDepth)source-side reverb ray depth 조회
exaSoundSourceSetReverbSendDb(id, db)source별 reverb send gain 설정
exaSoundSourceGetReverbSendDb(id, outDb)source별 reverb send gain 조회
exaSoundSourceSetPathEnable(id, pathType, enabled)source별 path type 활성화 제어
exaSoundSourceIsPathEnabled(id, pathType, outEnabled)source별 path type 활성화 상태 조회

width 또는 height0으로 두면 native는 listener ray count fallback을 사용할 수 있습니다. production tuning과 QA matrix에서는 명시적인 non-zero budget을 지정해 listener ray와 source reverb ray 비용을 분리해서 측정하세요.

Diffuse Scattering 옵션

ExaSTOption에 산란 관련 파라미터가 추가되어 시뮬레이션을 미세 조정할 수 있습니다.

필드의미
diffuseEnabled산란 처리 on/off
diffuseStartDepth산란을 시작할 반사 깊이 (기본 5)
diffuseMaxOffsetRadius산란 offset 반경
diffuseCurveA/B/C거리·각도에 대한 산란 곡선 계수
guideDiffuseEnabledGuide diffuse ray 사용
guideDiffuseListenerHeadRadius청취자 머리 반경 (기본 0.0875m)

HRTF

exaInit(); // loads the embedded default HRTF once
exaNewListener(); // renderer starts with the engine default HRTF
exaReset(); // releases renderer state and the default HRTF

좌표계 계약

엔진이 받는 위치·방향·메시는 다음 기준 프레임으로 해석됩니다(규범 명세는 exasoundC.h 상단 COORDINATE CONTRACT).

  • 프레임: 우수(right-handed), +Y up, -Z forward(리스너 정면), +X right.
  • 단위: meters. 거리 물리(ISO 9613-1 공기 흡음, d/343 지연)가 미터를 전제합니다. 비-미터 단위 host는 ExaSTOption.sceneRatio(host 길이단위→m, 0=기본 1.0)로 맞추거나 지오메트리를 업로드 전 사전 스케일합니다 — 혼용 금지(이중 스케일).
  • 메시 winding: CCW 정점 순서 → 바깥쪽 법선. 법선 부호가 반사/회절 분류를 결정하므로, 손방향(handedness)을 바꾸는 host는 업로드 시 winding(정점/인덱스 순서)을 보존해야 합니다.

엔진 프레임이 다른 host는 exaListenerSetCoordinateBasis(right, up, forward) 로 자기 축을 1회 선언할 수 있습니다. basis는 HRTF 단계에서만 적용되고(지오메트리는 frame-relative), 반사(det<0, 좌수↔우수)도 허용됩니다. 미호출 시 기본(identity)이라 기존 동작과 동일합니다.

결과 조회

형태함수
유효 경로 (시각화·디버깅)exaGetValidPathCount, exaGetValidPaths
정렬된 IR (컨볼루션 입력)exaGetSortedIRDatas
Guide Plane / Mirror Position (진단)exaPropagatorGetGuidePlanes, exaPropagatorGetMirrorPositions

ExaPathDatapos[0]=source, pos[1..N]=hit points, pos[N+1]=listener 순으로 저장됩니다.

Object Update Type

exaObjectSetUpdateType(objID, updateType);
// 0 = EXA_OBJECT_UPDATE_STATIC : 런타임 TLAS/BLAS 갱신 없음
// 1 = EXA_OBJECT_UPDATE_REFIT : deformation - BLAS refit + TLAS bounds 갱신
// 2 = EXA_OBJECT_UPDATE_REBUILD : topology 변경 - 재빌드
// 3 = EXA_OBJECT_UPDATE_DYNAMIC : transform만 변경 - TLAS instance 갱신

정적 오브젝트는 매 프레임 BVH refit 비용을 피할 수 있습니다.

Refit은 skinned animation처럼 topology가 유지된 채 vertex만 움직이는 geometry를 위한 정책입니다. vertex 자체는 mesh 쪽 2-call protocol로 올립니다.

exaMeshUpdateVertices(meshID, vertices, numVertices); // CPU skinning 경로
exaMeshRefit(meshID); // mesh BVH refit

numVerticesexaMeshSetData 시점의 vertex 개수와 정확히 같아야 하며, 다르면 EXA_ERR_INVALID_ARG로 거부됩니다. topology(triangle index)가 바뀌면 refit이 아니라 exaMeshSetData로 다시 빌드하고 object update type을 Rebuild로 두십시오.

진단·통계

함수용도
exaGetStatistics()광선·경로·시간 통계
exaPropagatorGetProfile(sceneID)전파 단계별 프로파일
exaPropagatorGetGuidePlanes/MirrorPositions알고리즘 내부 상태
exaGetMemoryTraceSnapshot()메모리 사용 스냅샷
exaGetLastError()마지막 에러 메시지

참고