混合位置音訊與 WebGL

Ilmari Heikkinen

簡介

本文將說明如何使用 Web Audio API 中的定位音訊功能,在 WebGL 場景中加入 3D 音效。為使音訊更加淺顯易懂,我也會介紹使用 Web Audio API 時可能產生的環境影響。如要進一步瞭解 Web Audio API,請參閱 Boris Smus 撰寫的「Getting started with 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 範例頁面上有幾個不錯的多普勒效果範例。

要取得事件監聽器和 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;
...

如要設定音效的聲音圓錐,請設定 Panner 節點的適當屬性。圓錐角度以度為單位,範圍為 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 和純音訊必須附加 GainNode。

我使用的最終音訊處理圖表會將物件中的音訊傳送至 GainNode,並用作直通混音器。我會從混合器將音訊傳遞至 ConvolverNode 和另一個 GainNode,用於控制純音訊的音量。ConvolverNode 會掛接到專屬的 GainNode,以控制相關的音訊音量。GainNode 的輸出端連接至主音量控制器。

...
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 模擬各種效果和環境,例如大教堂和密閉房間。

參考資料