混合位置音訊與 WebGL

Ilmari Heikkinen

簡介

本文將介紹如何使用 Web Audio API 中的位置音訊功能,將 3D 音效新增至 WebGL 場景。為了讓音訊更加可靠,我也會向你介紹 Web Audio API 對環境的影響。如要深入瞭解 Web Audio API,請參閱 Boris Smus 的開始使用 Web Audio API 一文。

如要執行位置音訊,請使用 Web Audio API 中的 AudioPannerNode。AudioPannerNode 會定義聲音的位置、方向和速率。此外,Web Audio API 音訊結構定義具有事件監聽器屬性,可讓您定義事件監聽器的位置、方向和速率。透過這兩種方式,您可以使用膠囊效果和 3D 平移功能建立定向音效。

我們來看看上述場景的音訊程式碼。這是非常基本的 Audio API 程式碼。您可以建立多個 Audio API 節點,並將它們互相連結。音訊節點是個別音效、音量控制器、效果節點和分析工具等。建立這張圖表後,您必須將圖表連接至音訊內容目的地,才能聽到聲音。

// Detect if the audio context is supported.
window.AudioContext = (
  window.AudioContext ||
  window.webkitAudioContext ||
  null
);

if (!AudioContext) {
  throw new Error("AudioContext not supported!");
} 

// Create a new audio context.
var ctx = new AudioContext();

// Create a AudioGainNode to control the main volume.
var mainVolume = ctx.createGain();
// Connect the main volume node to the context destination.
mainVolume.connect(ctx.destination);

// Create an object with a sound source and a volume control.
var sound = {};
sound.source = ctx.createBufferSource();
sound.volume = ctx.createGain();

// Connect the sound source to the volume control.
sound.source.connect(sound.volume);
// Hook up the sound volume control to the main volume.
sound.volume.connect(mainVolume);

// Make the sound source loop.
sound.source.loop = true;

// Load a sound file using an ArrayBuffer XMLHttpRequest.
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function(e) {

  // Create a buffer from the response ArrayBuffer.
  ctx.decodeAudioData(this.response, function onSuccess(buffer) {
    sound.buffer = buffer;

    // Make the sound source use the buffer and start playing it.
    sound.source.buffer = sound.buffer;
    sound.source.start(ctx.currentTime);
  }, function onFailure() {
    alert("Decoding the audio buffer failed");
  });
};
request.send();

位置

位置音訊會根據音訊來源的位置和事件監聽器位置,決定如何將音訊混和在喇叭中。左揚聲器左側的音訊來源會變大,左揚聲器對音訊來源也會變大。

如要開始使用,請建立音訊來源並附加至 AudioPannerNode。然後設定 AudioPannerNode 的位置。現在,你已有一個可以移動的 3D 音效。音訊環境監聽器位置預設為 (0,0,0),因此使用這項功能時,AudioPannerNode 位置會與相機位置相關。每次移動攝影機時,都必須更新 AudioPannerNode 位置。如要讓 AudioPannerNode 相對於世界的位置,需要將音訊背景監聽器位置變更為攝影機位置。

如要設定位置追蹤,我們必須建立 AudioPannerNode,並將它連接至主要磁碟區。

...
sound.panner = ctx.createPanner();
// Instead of hooking up the volume to the main volume, hook it up to the panner.
sound.volume.connect(sound.panner);
// And hook up the panner to the main volume.
sound.panner.connect(mainVolume);
...

在每個影格中,更新 AudioPannerNodes 的位置。下例中會使用 Three.js。

...
// In the frame handler function, get the object's position.
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);

// And copy the position over to the sound of the object.
sound.panner.setPosition(p.x, p.y, p.z);
...

如要追蹤事件監聽器的位置,請將音訊內容的監聽器位置設為與攝影機位置相符。

...
// Get the camera position.
camera.position.set(newX, newY, newZ);
camera.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(camera.matrixWorld);

// And copy the position over to the listener.
ctx.listener.setPosition(p.x, p.y, p.z);
...

速度

現在,我們已找到事件監聽器的位置和 AudioPannerNode,讓我們留意它們的運作速度。只要變更事件監聽器的速率屬性和 AudioPannerNode,即可為音效加入偶像效果。您可以在 Web Audio API 的範例頁面找到一些不錯的 Doppler 效果範例。

如要取得事件監聽器和 AudioPannerNode 的速度,最簡單的方法就是追蹤每個影格的位置。事件監聽器的速率是指相機目前的位置減去相機在上一個影格中的位置。同樣地,AudioPannerNode 的速率是其目前位置減去先前位置。

如要追蹤速率,可以取得物件先前的位置,然後減去目前位置,然後將結果除以上一個影格後經過的時間。以下說明如何在 Three.js 中進行設定:

...
var dt = secondsSinceLastFrame;

var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
var px = p.x, py = p.y, pz = p.z;

object.position.set(newX, newY, newZ);
object.updateMatrixWorld();

var q = new THREE.Vector3();
q.setFromMatrixPosition(object.matrixWorld);
var dx = q.x-px, dy = q.y-py, dz = q.z-pz;

sound.panner.setPosition(q.x, q.y, q.z);
sound.panner.setVelocity(dx/dt, dy/dt, dz/dt);
...

方向

方向是指音效來源指向的方向和麵對的方向。透過螢幕方向,你可以模擬定向音訊來源。舉例來說,不妨想一個定向的講者。如果站在揚聲器前面,聲音就會比站在揚聲器後方的音量大聲大。更重要的是,您需要事件監聽器方向來判斷聲音來自哪個事件監聽器。轉動時,左側音訊必須切換到右端。

如要取得 AudioPannerNode 的方向向量,您需要取得發音 3D 物件的模型矩陣旋轉部分,然後將 vec3(0,0,1) 乘以其終點。為了設定情境監聽器的方向,您必須取得相機的方向向量。事件監聽器方向也需要使用上向量,因為需要知道事件監聽器頭的滾動角度。如要計算事件監聽器方向,請取得相機檢視矩陣的旋轉部分,然後將方向的 vec3(0,0,1) 乘以上向量的 vec3(0,-1,0)。

如要讓音訊方向產生效果,你還需要定義錐體。聲音錐則採內角、外側角度和外部增加。音效會以內部角度的正常音量播放,並在接近外角時逐漸增加外部增益。聲音是從外角的增益處播放。

在 Three.js 中追蹤方向會比較困難,因為這牽涉到某些向量數學,並將轉譯部分零碎處理為 4x4 世界矩陣。儘管如此,還是有不好幾行程式碼。

...
var vec = new THREE.Vector3(0,0,1);
var m = object.matrixWorld;

// Save the translation column and zero it.
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the 0,0,1 vector by the world matrix and normalize the result.
vec.applyProjection(m);
vec.normalize();

sound.panner.setOrientation(vec.x, vec.y, vec.z);

// Restore the translation column.
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

此外,相機方向追蹤也需要使用上向量,因此您必須使用變形矩陣乘以上方向量。

...
// The camera's world matrix is named "matrix".
var m = camera.matrix;

var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the orientation vector by the world matrix of the camera.
var vec = new THREE.Vector3(0,0,1);
vec.applyProjection(m);
vec.normalize();

// Multiply the up vector by the world matrix.
var up = new THREE.Vector3(0,-1,0);
up.applyProjection(m);
up.normalize();

// Set the orientation and the up-vector for the listener.
ctx.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);

m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

如要為聲音設定音效,請設定平移器節點的適當屬性。圓錐角是以度為單位,並介於 0 至 360 之間。

...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...

全部整合

全部結合在一起後,音訊情境事件監聽器會遵循攝影機的位置、方向和速率,而 AudioPannerNodes 會遵循各自音訊來源的位置、方向和速率。您必須更新每個影格中的 AudioPannerNodes 位置、速度與方向,以及音訊內容事件監聽器。

環境影響

配置好音訊功能後,你可以設定音訊的環境效果,增添 3D 場景的沉浸感。假設場景位於大型大教堂內,預設設定時,現場的聲音聽起來像是站在戶外。視覺和聽覺性的差異是視覺上的干擾,並降低了場景的視覺效果。

Web Audio API 的 ConvolverNode 可讓您設定音效的環境效果。並將其新增至音訊來源的處理圖,接著就能調整出符合設定效果的音效。您可以從網路上找到可以與 ConvolverNodes 搭配使用的衝動回應範例,也可以建立自己的回應範例。你可能會覺得遊戲體驗有點麻煩,因為你可以記錄模擬地點的衝動回應,但如果有需要,這項功能就留出去了。

如要使用 ConvolverNodes 來執行環境音訊,就必須重新接上音訊處理圖表。您必須透過 ConvolverNode 轉送音效,而不是將音效直接傳送至主要磁碟區。而且您可能想控制環境效應的強度,因此您也必須在 ConvolverNode 周圍傳遞音訊。如要控制 ConvolverNode 和純音訊的混合磁碟區,都需要將 GainNodes 連接至它們。

我使用的最後一段音訊處理圖,包含透過 GainNode 傳入物件的音訊,做為直通式混合器。我會透過 Mixer 將音訊傳遞至 ConvolverNode,以及另一個用來控制純音訊音量的 GainNode。ConvolverNode 會掛接至自己的 GainNode,以控制牽動音訊的音量。GainNodes 的輸出內容會連線至主磁碟區控制器,

...
var ctx = new webkitAudioContext();
var mainVolume = ctx.createGain();

// Create a convolver to apply environmental effects to the audio.
var convolver = ctx.createConvolver();

// Create a mixer that receives sound from the panners.
var mixer = ctx.createGain();

sounds.forEach(function(sound){
  sound.panner.connect(mixer);
});

// Create volume controllers for the plain audio and the convolver.
var plainGain = ctx.createGain();
var convolverGain = ctx.createGain();

// Send audio from the mixer to plainGain and the convolver node.
mixer.connect(plainGain);
mixer.connect(convolver);

// Hook up the convolver to its volume control.
convolver.connect(convolverGain);

// Send audio from the volume controls to the main volume control.
plainGain.connect(mainVolume);
convolverGain.connect(mainVolume);

// Finally, connect the main volume to the audio context's destination.
volume.connect(ctx.destination);
...

為了讓 ConvolverNode 可以運作,您需要將衝動回應樣本載入緩衝區,並讓 ConvolverNode 使用這個形式。範例載入方式與使用一般音訊樣本的方式相同。以下是其中一種方法:

...
loadBuffer(ctx, "impulseResponseExample.wav", function(buffer){
  convolver.buffer = buffer;
  convolverGain.gain.value = 0.7;
  plainGain.gain.value = 0.3;
})
...
function loadBuffer(ctx, filename, callback) {
  var request = new XMLHttpRequest();
  request.open("GET", soundFileName, true);
  request.responseType = "arraybuffer";
  request.onload = function() {
    // Create a buffer and keep the channels unchanged.
    ctx.decodeAudioData(request.response, callback, function() {
      alert("Decoding the audio buffer failed");
    });
  };
  request.send();
}

摘要

在本文中,您學到瞭如何使用 Web Audio API 將位置音訊加入 3D 場景。Web Audio API 可讓您設定音訊來源和事件監聽器的位置、方向和速率。只要設定這些圖片來追蹤 3D 場景中的物件,就能為您的 3D 應用程式建立豐富的聲景。

如要進一步提升音訊體驗,您可以使用 Web Audio API 中的 ConvolverNode,設定環境的一般音效。你可以使用 Web Audio API 模擬大教堂和封閉式房間等各種特效和環境。

參考資料