Highlight
このセッションは宇宙船ゲームを例に、RealityKit オーディオ API の全機能を段階的に示します。宇宙船のサウンドデザインは多層構造で、各エンジンに蒸気の尾、排気管、タービンの 3 層の音があり、ファイル再生とリアルタイム生成の 2 方式で実装されています。
主要内容
空間コンピューティングアプリで最もよくある問題は、3D オブジェクトは立体的に見えるのに音が平坦なことです。宇宙船が頭上を飛んでも音量が距離に応じて変化せず、方向感もない—体験はすぐに破綻します。RealityKit のオーディオ API は根本からこれを解決します。Entity.playAudio で再生するすべての音はデフォルトで空間化され、6 自由度レンダリング、物理ベースの距離減衰、実環境をリアルタイムでシミュレートしたリバーブが付きます。
このセッションは宇宙船ゲームでオーディオツールチェーン全体を示します。宇宙船には 2 つのエンジンがあり、各エンジンに 3 層の音があります。蒸気の尾はループファイル再生、排気管の音量はスロットルに追従、タービンは AU オーディオユニットでリアルタイム生成。衝突音は AudioFileGroupResource で材質ペアリングに応じて選択され、ゲインは衝突速度で決まります。没入環境に入ると ReverbComponent がリバーブプリセットを切り替えます。BGM は 4 チャンネルレイアウトと AmbientAudioComponent でレンダリング。最後に AudioMixGroupsComponent でユーザーが各カテゴリの音量比率を調整できます。
最もシンプルな 2 行の再生から、指向性制御、リアルタイムオーディオ生成、材質駆動の衝突音、リバーブプリセット、環境音楽、ミックスグループまで—空間コンピューティングオーディオのほぼすべてのシナリオをカバーします。
詳細
空間オーディオファイル再生
最も基本的な使い方は 2 行です(03:11):
func playVaporTrailAudio(from engine: Entity) async throws {
let resource = try await AudioFileResource(named: "VaporTrail")
engine.playAudio(resource)
}
AudioFileResource(named:)で Bundle からオーディオファイルを読み込むengine.playAudio(resource)でエンジンエンティティ上で再生し、自動的に空間レンダリング
再生即空間化—追加設定不要。2 つの詳細に注意:オーディオファイルはモノラルにすべき(空間化前にモノラルにダウンミックスされるため、事前にモノラルファイルにするとダウンミックスのアーティファクトを避けられる)。短いファイルにループとランダム開始位置を組み合わせると、同じ音源でも異なって聞こえる(04:02):
let resource = try await AudioFileResource(
named: "VaporTrail",
configuration: AudioFileResource.Configuration(
shouldLoop: true,
shouldRandomizeStartTime: true
)
)
let controller: AudioPlaybackController = engine.playAudio(resource)
controller.gain = -.infinity
controller.fade(to: .zero, duration: 1)
shouldLoop: trueで短いファイルをシームレスにループshouldRandomizeStartTime: trueで 2 つのエンジンがファイルの異なる位置から開始controller.gain = -.infinityで無音から開始し、ループ時のポップを回避controller.fade(to: .zero, duration: 1)で 1 秒かけて標称音量にフェードイン
指向性(Directivity)と独立オーディオソース
エンジン音に方向感を持たせる—正面では明るく、背面ではこもる—には SpatialAudioComponent の directivity プロパティを設定します(04:02):
let audioSource = Entity()
audioSource.orientation = .init(angle: .pi, axis: [0, 1, 0])
audioSource.components.set(
SpatialAudioComponent(directivity: .beam(focus: 0.25))
)
engine.addChild(audioSource)
let controller = audioSource.playAudio(resource)
- 独立した
Entityをオーディオソースとして作成し、エンジンの子ノードに—位置はエンジンに追従、方向は独立制御 .beam(focus: 0.25)でビーム指向性を設定。focus は 0(全方向)から 1(狭いビーム)。0.25 は軽い方向感- audioSource を回転させ、エンジン噴口方向から音が出るようにする
スロットル駆動ゲイン
排気管の音量はスロットルに線形追従(07:10):
func updateAudio(for exhaust: Entity, throttle: Float) {
let gain = decibels(amplitude: throttle)
exhaust.components[SpatialAudioComponent.self]?.gain = Audio.Decibel(gain)
}
func decibels(amplitude: Float) -> Float { 20 * log10(amplitude) }
- 線形スロットル値(0~1)を対数デシベルに変換し、人間の聴覚に合わせる
SpatialAudioComponentのgainを直接変更—Component プロトコルに準拠するため、カスタム System で毎フレーム更新可能
リアルタイムオーディオ生成(AudioGeneratorController)
タービン音はカスタム AU オーディオユニットでリアルタイム生成(08:17):
var turbineController: AudioGeneratorController?
func playTurbineAudio(from engine: Entity) {
let audioUnit = try await AudioUnitTurbine.instantiate()
let configuration = AudioGeneratorConfiguration(layoutTag: kAudioChannelLayoutTag_Mono)
let format = AVAudioFormat(
standardFormatWithSampleRate: Double(AudioGeneratorConfiguration.sampleRate),
channelLayout: .init(layoutTag: configuration.layoutTag)!
)
try audioUnit.outputBusses[0].setFormat(format)
try audioUnit.allocateRenderResources()
let renderBlock = audioUnit.internalRenderBlock
turbineController = try engine.playAudio(configuration: configuration) {
isSilence, timestamp, frameCount, outputData in
var renderFlags = AudioUnitRenderActionFlags()
return renderBlock(&renderFlags, timestamp, frameCount, 0, outputData, nil, nil)
}
}
AudioUnitTurbineはカスタム AU。内部で複数オシレーターがスロットル値に応じて音を生成AudioGeneratorConfigurationでモノラル出力を設定engine.playAudio(configuration:callback:)のコールバックでオーディオバッファに直接書き込み- 返された
AudioGeneratorControllerは保持必須。さもないとオーディオが停止
距離減衰のカスタマイズ
宇宙船が近づいたときだけ客室音楽が聞こえるようにするには、距離減衰を強める(11:28):
func configureDistanceAttenuation(for spaceshipHifi: Entity) {
spaceshipHifi.components.set(
SpatialAudioComponent(
gain: -18,
distanceAttenuation: .rolloff(factor: 4)
)
)
}
.rolloff(factor: 1)は自然減衰のデフォルト。factor 4 は自然の 4 倍速で減衰gain: -18でさらに音量を下げ、極近距離でのみ聞こえるようにする
材質駆動の衝突音
衝突音は 2 物体の材質ペアリングで選択(15:04):
func handleCollisionBegan(_ collision: CollisionEvents.Began) {
guard
let audioMaterials = audioMaterials(for: collision),
let resource: AudioFileGroupResource = collisionAudio[audioMaterials]
else {
return
}
let controller = collision.entityA.playAudio(resource)
controller.gain = relativeLoudness(for: collision)
}
AudioFileGroupResourceは複数バリアントを含み、再生ごとにランダム選択で反復感を回避relativeLoudness(for:)で衝突速度からゲインを計算- 材質はカスタム
AudioMaterialComponentでエンティティに付与(rock、plastic、metal など) - 実世界の衝突は ARKit シーン再構築メッシュの面分類を材質列挙にマッピング
リバーブプリセット
仮想環境に入るときにリバーブを設定(17:18):
func prepareStudioEnvironment() async throws {
let studio = try await Entity(named: "Studio", in: studioBundle)
studio.components.set(
ReverbComponent(reverb: .preset(.veryLargeRoom))
)
rootEntity.addChild(studio)
}
- エンティティ階層に
ReverbComponentを置けば有効。同時に 1 つのリバーブのみアクティブ - プログレッシブ没入では、リアル音響とプリセットリバーブが没入度に応じて混合
- フル没入では空間音源が完全にプリセットリバーブを使用
環境音楽とミックスグループ
4 チャンネルレイアウトの音楽は AmbientAudioComponent でレンダリング(20:05):
let resource = try await AudioFileResource(
named: "JoyRideMusic",
configuration: .init(
loadingStrategy: .stream,
shouldLoop: true
)
)
entity.components.set(AmbientAudioComponent())
entity.playAudio(resource)
loadingStrategy: .streamで大ファイルをストリーミングし、メモリ使用量を削減AmbientAudioComponentは 3 自由度レンダリング:頭部と音源の回転に応答、移動には応答しない- 4 チャンネルファイルはチャンネルレイアウトをファイルに書き込む必要があり、RealityKit が各チャンネルを正しい角度からレンダリングできる
ミックスグループでユーザーが各カテゴリを独立制御(21:57):
let resource = try await AudioFileResource(
named: "JoyRideMusic",
configuration: .init(
loadingStrategy: .stream,
shouldLoop: true,
mixGroupName: "Music"
)
)
var audioMixerEntity = Entity()
func updateMixGroup(named mixGroupName: String, to level: Audio.Decibel) {
var mixGroup = AudioMixGroup(name: mixGroupName)
mixGroup.gain = level
let component = AudioMixGroupsComponent(mixGroups: [mixGroup])
audioMixerEntity.components.set(component)
}
mixGroupName: "Music"でリソースを Music グループに分類AudioMixGroupsComponentは Component プロトコルに準拠。UI 操作やカスタム System から更新可能
重要ポイント
-
各音源に独立した Entity 子ノードを作成:視覚エンティティの位置と向きは音の伝播に適さない場合がある。オーディオソースを子ノードとしてマウントすれば、音の方向と位置を独立制御できる。例:エンジン中心ではなく噴口からエンジン音を出す。始め方:方向制御が必要な音源用に
Entity()を新規作成し、orientationとSpatialAudioComponentを設定してから視覚エンティティにaddChild。 -
AudioFileGroupResource で衝突音の機械感を排除:同じ衝突音セットに複数の微妙なバリアントを用意し、再生ごとにランダム選択。ゲインは衝突速度の対数デシベルで駆動—遅い衝突は静か、速い衝突は力強い。始め方:各材質ペアリング用に 3~5 バリアントファイルを作成し、
AudioFileGroupResourceを構築。CollisionEvents.Beganコールバックで速度からゲインを計算。 -
大ファイルは
.stream、短ファイルはshouldLoop+shouldRandomizeStartTime:10 秒のループファイルは 5 分の完全オーディオより大幅にメモリ節約。異なる開始位置で同相叠加を回避。始め方:継続的背景音は短いループファイルにし、shouldLoop: true+shouldRandomizeStartTime: true。大きな BGM はloadingStrategy: .stream。 -
AudioMixGroupsComponent で音量制御をユーザーに委ねる:効果音、音楽、音声を異なる Mix Group に分け、ユーザーが好みに応じて独立調整。始め方:
AudioFileResource.ConfigurationでmixGroupNameを設定し、専用のaudioMixerEntityにAudioMixGroupsComponentを保持。UI スライダーで各グループのgainを更新。
関連セッション
- Discover RealityKit APIs for iOS, macOS, and visionOS — このセッションのサンプルゲームの完全構築。物理力効果、ジョイント、ポータル通過など
- Build a spatial drawing app with RealityKit — 空間描画アプリによる別の RealityKit 実践事例
- Compose interactive 3D content in Reality Composer Pro — Reality Composer Pro で 3D コンテンツを編成。オーディオワークフローと連携可能
- Enhance the immersion of media viewing in custom environments — Destination Video サンプルでのリバーブプリセットの具体的用法
コメント
GitHub Issues · utterances