【UE5】Cityサンプルにおける音風景（Soundscape）の作り方

https://qiita.com/marv_kurushimay/items/2cfcafafea34a420e3a4

前回はCityサンプルにおけるワークフローについて書きました。あとがきでも触れましたが、サウンド環境の構築でも興味深い実装がされていたので、それを調査した結果をまとめてみます。

今回も同じく、UE5.0.0がリリースされた直後のデータを元に調査をしています。

# 概要
https://www.unrealengine.com/marketplace/ja/product/city-sample

Cityサンプルでは、サウンド面でも負荷を軽くする仕掛けがありました。

## Cityサンプルのドキュメント
https://docs.unrealengine.com/5.0/ja/city-sample-project-unreal-engine-demonstration/

まずは、上記のCityサンプルに関するドキュメント内、[街のサウンドスケープとプロシージャルなオーディオ生成](https://docs.unrealengine.com/5.0/ja/city-sample-project-unreal-engine-demonstration/#%E8%A1%97%E3%81%AE%E3%82%B5%E3%82%A6%E3%83%B3%E3%83%89%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B7%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%AB%E3%81%AA%E3%82%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AA%E7%94%9F%E6%88%90)の項を参照することをお勧めします。
ただ、ドキュメントを見ても正直良く分からない点が多いので、実装を確認しつつそれぞれの機能が何をしているのかをまとめます。

# HoudiniとUnrealEngineのつながり
## pbc(alembic形式)の出力
Houduniでは、PointCloudの各Pointに情報を埋め込んで、Houdini上でpbcとして出力されています。ここではUnrealEngineは一切関係ありません。

:::note info
Cityサンプルで使用されているHoudiniプロジェクトは、Cityサンプルフォルダ内のCitySample_HoudiniFiles.zipにまとめられています。
使い方に関しては他の記事でも紹介しています、ずしさんの[Tweetのスレッド](https://twitter.com/shiba_zushi/status/1512428261384818698)にまとめられているので参考にしてください。

SmallCityのHoudiniプロジェクトが入っていて、UnrealEngine側の/Game/Map/Small_City_LVLの内容と対応していますので、見比べながらデータを見ていくと理解しやすいと思います。
:::

## RuleProcessorでActorを生成
Houdiniで出力したpbcを、UnrealEngineで読み込みます。その際に使われているのがRuleProcessorです。
RuleProcessorに関しては

https://qiita.com/marv_kurushimay/items/2cfcafafea34a420e3a4

の記事にまとめているので、参照してください。
ここで使われているGenerateWorldAudioDataActorでは、まずTileItratorのルールで、PointCloudの範囲全体を60×60×1（縦×横×高さ）の計3600タイルに分割しています。その後、各タイルごとにWorldAudioDataSystemに用意されているWorldAudioDataClusterActorを生成するようなルールが用意されています。
ルールの実装もWorldAudioDataSystemのFSpawnWorldAudioDataRuleDataで実装されており、この辺はWorldAudioDataSystemで実装が完結しています。

## WorldPartitionでロード管理
これでHoudiniで生成したポイントの一定の塊(Cluster)ごとに、**WorldAudioDataClusterActor**が生成されました。
Actorになっているので、あとはWorldPartitionでストリーミング管理されて、プレイヤーの位置に応じて自動的にLoad/Unloadされます。

# WorldAudioDataSystemでやってること
## PointCloudとSoundscapeの橋渡し
Houdiniから出力されるAudio用のPointCloud情報(pbc)では、座標や回転などの基本データのほかにtypeとreverbの値だけが記録されています。

このうち、typeの値はColorPointの種別を保持しています。typeとColorPointの情報の関連付けは、WorldAudioDataの設定にあります。
UWorldAudioDataSettings::MantleTypeToSoundscapeColorPointMapで管理されていて、
![pbc_type.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/29468d51-29c0-ae3d-2527-3ec6d2359139.png)
pbcに含まれるtypeは左側(Key)、Soundscapeで参照するGamePlayTagは右側(Value)として設定されています。

このGamePlayTagを使って、Soundscape側でどういう処理をしているかは、別項でまとめます。

## reverbの管理
このうち、reverbの値は各Pointにおけるreverb値を保持しており、UWorldAudioDataSubsystem::DetermineReverbValueAtLocation()などでListenerPointにおけるreverb値を求めるのに使用しています。

求めたreverb値を元に、
+ UWorldAudioDataSettings::MantleEffectsMetaDataKey
+ UWorldAudioDataSettings::MaxEffectAltitude
+ UWorldAudioDataSettings::EffectMap

などの設定を使って、reverbの値を制御しています。
reverb自体の制御はUE標準のサウンドシステム（SoundSubmix/SubmixEffects）を使って行われています。

## WorldAudioDataContinuousSoundについて
ワールド内で継続的に再生されている音（例えば交通量の多いエリアの環境音、強めの風の音、街中の環境音）などもWorldAudioDataSystemで管理しています。
![ContinuousSound.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/0da84780-c634-d718-2e8f-d84a571dd024.png)
設定は上図のようにtypeとそれに対応するデータで管理されています。
ContinuousSoundMapに書かれている上位3つ(freeway/interchange/shoreline)は、PointCloud経由で配置されるデータを示していて、pbcのtypeと対応してルールプロセッサーで生成されたActorから登録されていました。
下位３つは、ListenerLocationで登録されるContinuousSoundです。登録名を見ると
+ 街中(sfx_amb_ **Urban_Air** _lp_meta)
+ 高高度(sfx_amb_Pawn_ **HiAltAir** _lp_meta)
    + Flyモードで高高度に移動したとき？
+ 風切り音(sfx_amb_Pawn_ **Wind** _lp_meta)
    + Flyモードの移動中？

と、名前からすると上記のような音なのですが
![ContinuousPawnSound.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/2ac2bd7f-816e-1250-aa48-f279d3022505.png)
SoundWaveのデバッグ表示を見るとVolumeは常に1.0で鳴りっぱなしのようです。
状況によって変化させているようにも聞こえるのですが、他の音が無くなるせいでそう聞こえているだけかもしれません。

## 車両(Vehicle)のAudio管理
### 種別におけるサウンドプリセットデータの指定
![MASSTraffic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/850f8f52-3bae-253d-f832-59d0129a0fad.png)
MASSTrafficから生成される各種車の種別に応じたサウンド設定アセットの割り当て表があります。この表を元に車を音源とする各種サウンドを構築していきます
ちなみに、これらのプリセットアセットには
+ エンジン音
+ ブレーキ音（未使用）
+ クラクション

などが設定できるようになっていました。

### AudioControllerの位置追跡
前項のプリセットデータから生成されたAudioControllerの位置情報更新も、WorldAudioDataSystemが行っています。
UpdateのタイミングでMassEntityから車両の位置情報を収集。そこに含まれるIdを照合して位置情報を更新しています。

車両から発生するエンジン音など具体的な音は、ここで追跡されている座標から鳴らしているようです。
ただし、Soundscapeの項でも触れますが、プリセットで設定している以外の音、例えば移動時の走行音（エンジン音除く）は物体の座標からは再生されていません。

### 再生制御
エンジン音の制御やクラクションなどもWorldAudioDataSystem内(UWorldAudioDataVehAudioController)で行っています。
具体的には、
+ エンジン音
    + 車両スピードなど、MetaSoundへのパラメータ設定
    + Playerキャラとの相対座標を元に、Pitchの上げ下げ（ドップラー効果？）
+ クラクション
    + プレイヤーが一定範囲にいるとクラクションを再生する

といった制御を行っていました。

## MassEntity関連
Vehicle（車両）、Pedestrian（歩行者）由来のサウンドもMassEntityから情報を取得して、SoundscapeのColorPointを追加する処理も行っていました。

# Soundscapeでやっていること
## Color/ColorPointとは？
既に何度か出ていますが、SoundscapeにおけるColor/ColorPointについて軽く説明しておきます。

私もはっきり分かっていませんが、Colorは発生する音の設定、ColorPointは発生元の情報を示しているようです。
名前としてSoundTypeやSoundPointの方が適切な気もしますが、音色(Color of Sound)辺りからとってるのかもしれません。

このColorはSoundWaveアセットの指定の他、再生制御関連の情報も含んでいます。ColorPointは座標と発生元の種類（ビル、歩道、埠頭、などなど）を保持/管理しています。

## Soundscapeのデータ管理構造
### SoundscapePalette
SoundscapeColorが複数格納されているアセットです。Cityサンプルのデータでは「人ごみの音(/Game/Audio/Soundscape/CityCrowdsPalette)」「道路関連の音(/Game/Audio/Soundscape/CityRoadsPalette)」といったカテゴリごとにアセットが作られていました。
また、登録したSoundscapeColor毎にフェードイン/アウトの時間、ピッチ、ボリュームの設定を追加できるようです。

Cityサンプルでは、Play（正確にはOnEnterSandboxイベント）時にCitySampleWorldAudioDataScriptでアセットとして用意されている8種のSoundscapePaletteが登録されていました。

### SoudscapeColor
このSoundscapeColorが、サウンドの調整パラメータ(ModulationBehavior)、再生パラメータ(PlaybackBehavior)、発生条件(SpawnBehavior)の設定を管理しています。
![SoundscapeColor.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/68cb841d-f8f9-dd23-6ea0-adcb0252f6fa.png)
カテゴリ毎にざっと紹介をしていきます。
#### SoundscapeColor
エンジン標準のSoundWaveアセットを指定します。
加えて、ベースのVolume、Pitchも指定できるようになっていました。
#### ModulationBehavior
VolumeやPitchなど設定された音（アセット）の状態から調整するパラメータがまとめられています。
#### PlaybackBehavior
再生時間や再生開始時間をずらすパラメータがまとめられています。
#### SpawnBehavior
以下のようなパラメータが並んでいました
+ 初回発生時の待機
    + 発生待機時間のランダム範囲
+ 継続的に発生させるかどうか
    + 次回発生までの待機時間のランダム範囲
+ 発生距離
    + リスナーからの発生距離レンジ
+ 発生角度
    + リスナー正面からの発生角度
+ 高度制限
    + 最高/最低の高度
    + 基本は高度もランダムだが、制限が可能
+ サウンドソースの回転
    + 仰角、方位角のレンジ
+ トレースして発生させるか
    + コリジョンチャンネルを指定して、RayTraceの結果位置から発生させることができるようです
+ ColorPointのDensityを参照するか
    + あとの項目で詳しく説明しますが、ColorPointの密度を参照して一定以上の密度があれば発生させる仕組みです

これらのパラメータをみると、サウンドの発生位置はリスナー（≒プレイヤー）位置を基準にした周囲にランダムに生成していることが分かります。方向などを制限して後方だけに発生させるとか、上空だけに発生させる。といったことは出来ますが、近くの車から発生させる。とか、ビルの室外機の位置から発生させる。といった周囲の環境は基本的に考慮していません。

## ColorPointをHashで空間管理
WorldAudioDataSystemを経由してpbcより構築されたColorPointの情報は、座標情報を元にハッシュ化されます。
![ColorPointSystem.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/2b4182fe-8bff-d433-d363-70a32f711d99.png)
このハッシュは、Soundscape設定の設定を元に三段階のLODレベルで構築されます。HashWidthのサイズがハッシュグリッドの一辺のサイズです。
例えば、LOD1だと500.0と設定されていて、縦/横/高さが500.0(=5m)の空間は同じハッシュ値が割り当てられ管理されています。

 ColorPointの座標からハッシュを作成し、それを元に各グリッドにいくつの要素が入っているかというDensityとして扱われます。

## PointCloud以外のColorPoint
前項では「pbcより構築されたColorPointの情報は」として、PointCloudからの情報に限定しました。しかし、それ以外の経路でColorPointが追加されている個所もありました。
+ 動いている車両
+ 止まっている車両
+ 動いている歩行者
+ 止まっている歩行者

の4種です。
これらは、それぞれのポイントから音を出してもおかしくなさそうですが、実際はColorPointの管理だけになっているようです。
座標は逐次MassEntityから取得しているので、実際に置かれている物体（車や人）を追従していますが、Soundscapeのシステムではそれぞれで音は出していません。

実際に音を出すのは、CityCrowdsPaletteに設定されている各SoundscapeColorによる発生となっていて、処理負荷の軽減を図っているようです。

:::note info
> Soundscapeのシステムではそれぞれで音は出していません。

としましたが、車両(Vehicle)のAudio管理の項で説明した通り、車両はWorldAudioDataSystemの機能で、エンジン音などいくつかの音は物体の位置から再生しています。
また、通行人もアニメーション通知などを使って足音などは出していますので、これらも物体の位置から再生しています。

車の走行音や人々のざわめきなど、環境音的な音はそれっぽい位置から出ていれば十分。として、Soundscapeではデザインされているようです。
:::

## Density依存で音を再生
SoundscapeColorのSpawnBehaviorでDensity依存の設定(Filter by Color Point Density)を有効にすると、[ColorPointをHashで空間管理](#colorpointをhashで空間管理)の項で説明したColorPointのDensityを使って再生リクエストが制御されます。
他のSpawnBehaviorの発生条件を満たした後、再生位置での指定ColorPoint(GamePlayTag)のDensityを取得し、MinColorPointNumberと比較してDensityがその値以下であれば発生処理を中断します。
なお、ここで出てきたColorPoint(GamePlayTag)が、PointCloudとSoundscapeの橋渡しの項で触れたGamePlayTagの設定と一致している必要があります。

この機能で実現したいことですが、例えばSoundscapeColorは砂利の音を指定しているが周囲に砂利が無い場合、砂利の音を発生させるのは不自然です。
それを抑制するために、発生ポイントの周囲に一定以上のColorPointが配置されていないときは再生しないようにしています。

# まとめ
WorldAudioDataSystemは、HoudiniとSoundscapeだったり、MassEntityとSoundWaveだったり、他の機能との橋渡し役という印象でした。

Soundscapeに関しては、その名の通り音風景として、それっぽい環境音を構築するシステムでした。
あくまで環境音なので、物体の位置から再生していないというのも新たな発見でした。

弊社ではHoudiniからPointCloud経由で静的な音源配置ができないか。という目的のために、WorldAudioDataSystemやSoundscapeに目を付けて調べてみたのですが、その目的からするとはずれでした。
しかし、「大量に配置しつつも再生量を制御する仕組み」としてみると、この設計はサウンドに限らず応用の可能性がありそうです。

## UE5.1ではエンジンプラグイン化！
https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/682-soundscape-plug-play-state-based-ambient-sounds

また、先日Preview1がリリースされたUE5.1では、Soundscapeがエンジンプラグインとして取り込まれました。
今回のまとめはUE5.0のCityサンプルで調査した結果なので、また少し変わっているかもしれませんが、参考にはなるかと思います。