今回は、DirectX系のサウンドAPI「XAudio2」と「X3DAudio」を使って、音が鳴っている方向や距離を感じられる立体音響(3Dサウンド)を作る方法について解説していきます。
「敵の足音が『右後ろから』聞こえるようにしたい」
「XAudio2で音は鳴らせたけど、3D化はどうやるの?」
「X3DAudioって別物なの?XAudio2と何が違うの?」
こんな悩みはありませんか?
まず整理すると、XAudio2は「音を鳴らすエンジン」、X3DAudioは「3D空間での聞こえ方を計算する電卓」です。X3DAudioが「この音は左右・前後にどれくらい振り分ければいいか」を計算し、その結果をXAudio2に適用して実際に鳴らす、という役割分担になっています。
この記事では、2つのAPIの役割・立体音響の仕組み・リスナーとエミッターの考え方・実装の流れ・ハマりどころまで、C++の例つきで解説します。読み終えると、位置に応じて聞こえ方が変わる音を実装できるようになりますので、ぜひ最後まで読んでください。
XAudio2とX3DAudioの役割|エンジンと電卓
2つのAPIは名前が似ていますが、役割はきれいに分かれています。
- XAudio2… 音声データを再生する本体。ソースボイス(音源1つ)とマスターボイス(最終出力)で構成される
- X3DAudio… リスナー(耳)とエミッター(音源)の位置から、各スピーカーへの音量と周波数の補正値を計算する。音そのものは鳴らさない
つまり流れは「X3DAudioで計算 → 結果をXAudio2のソースボイスに適用」の繰り返しです。プレイヤーやカメラが動くたびに計算し直して適用することで、音の方向感が動的に変わります。
立体音響の仕組み|左右・距離・ドップラー
X3DAudioが計算してくれる「立体感」は、主に次の3つの要素でできています。
- 定位(パンニング)… 音源が右にあれば右スピーカーを大きく。左右・前後の振り分け
- 距離減衰… 遠いほど小さく。離れるにつれて自然に音量が下がる
- ドップラー効果… 近づく音は高く、遠ざかる音は低く(救急車のあの感じ)
これらを自分で計算するのは大変ですが、X3DAudioにリスナーとエミッターの位置・向き・速度を渡せば、まとめて計算してくれます。位置ベクトルの扱いでは正規化の知識も役立ちます。
リスナーとエミッター|耳と音源を定義する
3D音響の主役は2つの構造体です。リスナー(X3DAUDIO_LISTENER)=プレイヤーの耳、エミッター(X3DAUDIO_EMITTER)=音を出す物体。それぞれに位置・前方向・上方向・速度を設定します。
// リスナー(プレイヤー/カメラ)
X3DAUDIO_LISTENER listener = {};
listener.Position = { camX, camY, camZ }; // 耳の位置
listener.OrientFront = { 0.0f, 0.0f, 1.0f }; // 前方向
listener.OrientTop = { 0.0f, 1.0f, 0.0f }; // 上方向
listener.Velocity = { vx, vy, vz }; // 速度(ドップラー用)
// エミッター(音源)
X3DAUDIO_EMITTER emitter = {};
emitter.Position = { objX, objY, objZ };
emitter.OrientFront = { 0.0f, 0.0f, 1.0f };
emitter.OrientTop = { 0.0f, 1.0f, 0.0f };
emitter.ChannelCount = 1; // モノラル音源が基本
emitter.CurveDistanceScaler = 10.0f; // 距離減衰のスケール
emitter.DopplerScaler = 1.0f;ここで大事なのが、3D音響用の音源はモノラル(1チャンネル)にすることです。ステレオ音源は「もう左右が決まっている音」なので、方向を付け直せません。方向を持たせたい音は必ずモノラルで用意します。
実装の流れ|計算して適用する
準備ができたら、初期化時にX3DAudioInitializeで計算機を作り、毎フレームX3DAudioCalculateで計算 → 結果をソースボイスに適用します。
// 初期化(1回だけ)
X3DAUDIO_HANDLE x3dInstance;
X3DAudioInitialize(SPEAKER_STEREO, X3DAUDIO_SPEED_OF_SOUND, x3dInstance);
// 計算結果を受け取る入れ物
float matrix[8] = {}; // 出力チャンネル数ぶん
X3DAUDIO_DSP_SETTINGS dsp = {};
dsp.SrcChannelCount = 1; // モノラル音源
dsp.DstChannelCount = 2; // ステレオ出力
dsp.pMatrixCoefficients = matrix;// 毎フレーム:計算して適用
X3DAudioCalculate(x3dInstance, &listener, &emitter,
X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER,
&dsp);
// 左右の振り分け(音量マトリクス)を適用
sourceVoice->SetOutputMatrix(masterVoice, 1, 2, dsp.pMatrixCoefficients);
// ドップラーによる再生速度(ピッチ)を適用
sourceVoice->SetFrequencyRatio(dsp.DopplerFactor);この「計算して適用」をプレイヤーや音源が動くたびに回すだけで、方向・距離・ドップラーが効いた立体音響になります。毎フレームの更新なので、deltaTimeで速度を求めておくとドップラーも自然になります。
なお、SPEAKER_STEREOのような定数や関数はワイド文字ではありませんが、XAudio2まわりはWindows APIと同じくワイド文字(L付き)のファイルパスを扱う場面が多いので、あわせて押さえておくと安心です。
【重要】私が立体音響でハマった体験談
個人開発のホラーゲームで、敵の足音を3D化しようとしたのにどこに敵がいても左右がまったく動かず、ただ音量だけ変わる状態になりました。X3DAudioCalculateは呼んでいるのに、です。
原因は足音のwavがステレオだったことでした。ステレオはすでに左右が固定された音なので、いくら方向を計算しても振り分けようがなかったんです。足音をモノラルで書き出し直し、SrcChannelCount = 1にしたら、いきなり「右後ろから足音」が実現しました。
「3Dで鳴らす音はモノラル」という基本を、身をもって覚えた出来事でした。
立体音響のよくある失敗例と対処法
①左右に定位しない(音量しか変わらない)
体験談のとおり、音源がステレオなのが原因なことが多いです。3D化する音はモノラルで用意し、SrcChannelCountを1にしましょう。
②リスナーの向きを更新し忘れる
プレイヤーが振り向いてもOrientFrontを更新しないと、後ろの音が前から聞こえるなど方向が狂います。カメラの向きに合わせて毎フレーム、前方向・上方向を更新しましょう。
③距離を変えても音量が変化しない
CurveDistanceScalerが未設定(0)だと距離減衰が効きません。ゲームのスケールに合った値(メートル単位なら数〜数十)を設定し、X3DAUDIO_CALCULATE_MATRIXフラグを渡しているかも確認しましょう。
注意点
- 3D化する音はモノラルで用意する
- リスナー/エミッターの位置・向き・速度を毎フレーム更新する
- X3DAudioは計算するだけ。結果をXAudio2に適用して初めて鳴る
まとめ
- XAudio2=鳴らすエンジン、X3DAudio=聞こえ方を計算する電卓
- 立体感は定位・距離減衰・ドップラーの3要素でできている
- リスナーとエミッターを定義し、計算結果をソースボイスへ適用する
- 最大の落とし穴は「3D音はモノラル」
音が方向を持つと、ゲームの没入感は一段跳ね上がります。まずはモノラルの効果音1つをエミッターにして、プレイヤーの周りをぐるっと回してみてください。左右が動く瞬間は感動しますよ。


